拉勾网教育课程《52讲轻松搞定爬虫》笔记(最近有点忙,过一整子补上后续)
1。http基本原理
URI URLURN 超文本 HTTP/HTTPS http请求过程
iitiator请求源
Content-Type互联网媒体类型(提交数据的方式)
Expires响应的过期时间
Host:用于指定请求资源的主机 IP 和端口号,其内容为请求 URL 的原始服务器或网关的位置。从 HTTP 1.1 版本开始,请求必须包含此内容。
Referer:此内容用来标识这个请求是从哪个页面发过来的
Content-Type:也叫互联网媒体类型(InternetMediaType)或者MIME类型,在HTTP协议消息头中,它用来表示具体请求中的媒体类型信息。例如,text/html代...
构造 POST 请求,需要使用正确的 Content-Type,并了解各种请求库的各个参数设置时使用的是哪种 Content-Type,不然可能会导致 POST 提交后无法正常响应。
Set-Cookie:设置 Cookies。响应头中的 Set-Cookie 告诉浏览器需要将此内容放在 Cookies 中,下次请求携带 Cookies 请求。
2.夯实根基,Web 网页基础
网页可以分为三大部分:HTML、CSS 和 JavaScript。
如果把网页比作一个人的话,HTML 相当于骨架,JavaScript 相当于肌肉,CSS 相当于皮肤
#head_wrapper.s-ps-islite.s-p-top{
position:absolute;
bottom:40px;
width:100%...
选中 id 为 head_wrapper 且 class 为 s-ps-islite 的节点,然后再选中其内部的 class 为 s-p-top 的节点。
在 HTML 中,只需要用 link 标签即可引入写好的 CSS 文件
JavaScript 通常也是以单独的文件形式加载的,后缀为 js,在 HTML 中通过 script 标签即可引入
在CSS中,我们使用CSS选择器来定位节点。
例如,上例中div节点的id为container,那么就可以表示为#container,其中#开头代表选择id,其后紧跟id的名称
另外,如果我们想选择class为wrapper的节点,便可以使用.wrapper,这里以点“.”开头代表选择class,其后紧跟class的名称。
另外,还有一种选择方式,那就是根据标签名筛选,例如想选择二级标题,直接用 h2 即可
CSS 选择器还支持嵌套选择,各个选择器之间加上空格分隔开便可以代表嵌套关系
#container .wrapper p 则代表先选择 id 为 container 的节点,然后选中其内部的 class 为 wrapper 的节点,然后再进一步选中其内部的 p 节点。
如果不加空格,则代表并列关系,如div#container.wrapperp.text代表先选择id为container的div节点,然后选中其内部的class为wrappe...
第03讲:原理探究,了解爬虫的基本原理
获取网页 提取信息 保存数据 自动化持续
所见皆可爬
使用基本HTTP请求库得到的源代码可能跟浏览器中的页面源代码不太一样。对于这样的情况,我们可以分析其后台Ajax接口,也可使用Selenium、Splash这样的库来实现模拟JavaScript 渲染。
第04讲:基础探究,Session 与 Cookies
Session 在服务端,也就是网站的服务器,用来保存用户的 Session 信息;Cookies 在客户端,也可以理解为浏览器端
第一次请求服务器时,服务器会返回一个响应头中带有 Set-Cookie 字段的响应给客户端
成功登录某个网站时,服务器会告诉客户端设置哪些 Cookies 信息,在后续访问页面时客户端会把 Cookies 发送给服务器,服务器再找到对应的 Session 加以判断
Application 选项卡,然后在左侧会有一个 Storage 部分,最后一项即为 Cookies
Value,即该 Cookie 的值。如果值为 Unicode 字符,需要为字符编码。如果值为二进制数据,则需要使用 BASE64 编码
Path,即该 Cookie 的使用路径。如果设置为 /path/,则只有路径为 /path/ 的页面可以访问该 Cookie
Http字段,即Cookie的httponly属性。若此属性为true,则只有在HTTPHeaders中会带有此Cookie的信息,而不能通过document.cookie来访问此 Cookie
第05讲:多路加速,了解多线程基本原理
进程呢?它就是线程的集合
使用多线程,处理器就可以在某个线程等待的时候,去执行其他的线程,从而从整体上提高执行效率
IO 密集型任务。对于这种任务,如果我们启用多线程,处理器就可以在某个线程等待的过程中去处理其他的任务,从而提高整体的爬取效率
使用 Thread 类来创建一个线程,创建时需要指定 target 参数为运行的方法名称
想要主线程等待子线程运行完毕之后才退出,可以让每个子线程对象都调用下 join 方法
可以通过继承 Thread 类的方式创建一个线程,该线程需要执行的方法写在类的 run 方法里面即可
如果一个线程被设置为守护线程,那么意味着这个线程是“不重要”的,这意味着,如果主线程结束了而该守护线程还没有运行完,那么它将会被强制结束。
某个线程在对数据进行操作前,需要先加锁,这样其他的线程发现被加锁了之后,就无法继续向下执行,会一直等待锁被释放,只有加锁的线程把锁释放了,其他的线程才能继续加锁并对数据做修改,修改完了再释放锁
第06讲:多路加速,了解多进程基本原理
进程中 GIL 的存在,Python 中的多线程并不能很好地发挥多核优势,一个进程中的多个线程,在同一时刻只能有一个线程运行
进程是系统进行资源分配和调度的一个独立单位,所以各个进程之间的数据是无法共享的,如多个进程无法共享一个全局变量,进程之间的数据共享需要有单独的机制来实现
multiprocessing 提供了一系列的组件,如 Process(进程)、Queue(队列)、Semaphore(信号量)、Pipe(管道)、Lock(锁)、Pool(进程池)等,
multiprocessing中,每一个进程都用一个Process类来表示。它的API调用如下:
Process([group [, target [, name [, args [, kwargs]]]]])
target表示调用对象,你可以传入方法的名字。
args表示被调用对象的位置参数元组,比如target是函数func,他有两个参数m,n,那么args就传入[m,n]即可。
kwargs 表示调用对象的字典。
name 是别名,相当于给这个进程取一个名字。
group 分组。
args 必须要是一个元组,如果只有一个参数,那也要在元组第一个元素后面加一个逗号
可以通过 cpu_count 的方法来获取当前机器 CPU 的核心数量,通过 active_children 方法获取当前还在运行的所有进程
可以通过继承的方式创建一个进程类,进程的基本操作我们在子类的 run 方法中实现即可
如果一个进程被设置为守护进程,当父进程结束后,子进程会自动被终止,我们可以通过设置 daemon 属性来控制是否为守护进程
能不能让所有子进程都执行完了然后再结束呢?当然是可以的,只需要加入 join 方法即可,
子进程出现问题陷入了死循环,主进程也会无限等待下去。怎么解决这个问题呢?可以给 join 方法传递一个超时参数,代表最长等待秒数。如果子进程没有在这个指定秒数之内完成,会被强制返回,主进程不再会等待
也可以通过 terminate 方法来终止某个子进程,另外我们还可以通过 is_alive 方法判断进程是否还在运行
在调用 terminate 方法之后,记得要调用一下 join 方法,这里调用 join 方法可以为进程提供时间来更新对象状态,用来反映出最终的进程终止效果
进程互斥,避免了多个进程同时抢占临界区(输出)资源。我们可以通过 multiprocessing 中的 Lock 来实现
可以用 multiprocessing 库中的 Semaphore 来实现信号量
使用 Queue 作为进程通信的共享队列使用。
使用 Queue 实现了进程间的数据共享,那么进程之间直接通信,如收发信息,用什么比较好呢?可以用 Pipe,管道
默认声明 Pipe 对象是双向管道,如果要创建单向管道,可以在初始化的时候传入 deplex 参数为 False。
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行它。
记得调用 close 方法来关闭进程池,使其不再接受新的任务,然后调用 join 方法让主进程等待子进程的退出,等子进程运行完毕之后,主进程接着运行并结束
正则表达式
match
import re content = 'Hello 123 4567 World_This is a Regex Demo' print(len(content)) result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content) print(result) print(result.group()) print(result.span())
content = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^Hello\s(\d+)\sWorld',content) print(result) print(result.group()) print(result.group(1)) print(result.span())
content = 'Hello 123 4567 World_This is a Regex Demo' result = re.match('^Hello.*Demo$',content) print(result) print(result.group()) print(result.span())
\s空格
\d数字【0-9】
\w字母数字下划线
\n换行符
\t制表符
[...]一组字符
[^...]不在里面的字符
{n}匹配n个 {m,n}匹配m到n次
a|b匹配a或b
()将想要提取的子字符串扩起来
调用group方法传入分组的索引即可获取提取的结果
span()
\小些就是匹配什么
\大写就是不匹配什么
search
# 扫描整个字符串,返回第一个满足的 content = 'Hello 123 4567 World_This is a Regex Demo' result = re.match('^Hello.*Demo$',content) print(result)
# sub替换 content = 'Hello 123 4567 World_This is a Regex Demo' result = re.sub('Hello','',content,re.S) print(result)
#【小技巧】 在提取的过程中,先用sub的方法将a节点的标签去掉,再用findall提取
compile
# compile编译的方法 content = 'Hello 123 4567 World_This is a Regex Demo' pattern = re.compile('World_This') result = re.sub(pattern,'',content,re.S) print(result)