摘要
本文内容学习过程中记录
路飞爬虫课程第二天学习内容知识点整理
首先bs4是一个处理html很方便的一个模块,之前在处理html文本时使用的是re正则,但是和bs4相比负责程度不是一个等级,恨没有早点学习到这个模块。
之前也有了解过异步非阻塞的相关知识,但是一直没有能完全理解,经过今天的课程,终于知道了什么是异步非阻塞。
- bs4
- 爬虫本质
- 提高爬虫性能
- web微信
- io多路复用
- 异步非阻塞
一些常见的爬虫经验
图片防盗链
- referer
- cookies
轮询
浏览器每一段时间向浏览器发一次请求
缺点:不断的向服务器发请求,服务器的内存压力大, 效率低,存在时间差
长轮询
基于HTTP的长连接,是一种通过长轮询方式实现”服务器推”的技术,它弥补了HTTP简单的请求应答模式的不足,极大地增强了程序的实时性和交互性。
优缺点
- 轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
- 优点:后端程序编写比较容易。
- 缺点:请求中有大半是无用,浪费带宽和服务器资源。
- 实例:适于小型应用。
- 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
- 优点:在无消息的情况下不会频繁的请求,耗费资源小。
- 缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
- 实例:WebQQ、Hi网页版、Facebook IM。
- 长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
- 优点:消息即时到达,不发无用请求;管理起来也相对方便。
- 缺点:服务器维护一个长连接会增加开销。
- 实例:Gmail聊天
- Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
- 优点:实现真正的即时通信,而不是伪即时。
- 缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
- 实例:网络互动游戏。
实现原理
所谓长连接,就是要在客户端与服务器之间创建和保持稳定可靠的连接。其实它是一种很早就存在的技术,但是由于浏览器技术的发展比较缓慢,没有为这种机制的实现提供很好的支持。所以要达到这种效果,需要客户端和服务器的程序共同配合来完成。通常的做法是,在服务器的程序中加入一个死循环,在循环中监测数据的变动。当发现新数据时,立即将其输出给浏览器并断开连接,浏览器在收到数据后,再次发起请求以进入下一个周期,这就是常说的长轮询(long-polling)方式。
- 轮询的建立
- 建立轮询的过程很简单,浏览器发起请求后进入循环等待状态,此时由于服务器还未做出应答,所以HTTP也一直处于连接状态中。
- 数据的推送
- 在循环过程中,服务器程序对数据变动进行监控,如发现更新,将该信息输出给浏览器,随即断开连接,完成应答过程,实现“服务器推”。
- 轮询的终止
- 轮询可能在以下3种情况时终止:
- 有新数据推送
- 当循环过程中服务器向浏览器推送信息后,应该主动结束程序运行从而让连接断开,这样浏览器才能及时收到数据。
- 没有新数据推送
- 循环不能一直持续下去,应该设定一个最长时限,避免WEB服务器超时(Timeout),若一直没有新信息,服务器应主动向浏览器发送本次轮询无新信息的正常响应,并断开连接,这也被称为“心跳”信息。
- 网络故障或异常
- 由于网络故障等因素造成的请求超时或出错也可能导致轮询的意外中断,此时浏览器将收到错误信息。
- 轮询的重建
- 浏览器收到回复并进行相应处理后,应马上重新发起请求,开始一个新的轮询周期。
bs4(beautifulsoup4)
beautifuilsoup4: 用于解析html标签
安装
1 | pip install beautifulsoup4 |
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。
解释器
- html.parser
- Python的内置标准库
- 执行速度适中
- 文档容错能力强
- BeautifulSoup(markup, “html.parser”)
- Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
- lxml
- 速度快
- 文档容错能力强
- BeautifulSoup(markup, “lxml”)
- 需要安装C语言库
- xml
- 速度快
- 唯一支持XML的解析器
- BeautifulSoup(markup, [“lxml”, “xml”])
- BeautifulSoup(markup, “xml”)
- 需要安装C语言库
- html5lib
- 最好的容错性
- 以浏览器的方式解析文档
- 生成HTML5格式的文档
- BeautifulSoup(markup, “html5lib”)
- 速度慢
- 不依赖外部扩展
使用方法
1 | from bs4 import BeautifulSoup |
- name: 标签名
- id:标签的id属性
- attrs:标签的属性字典
- recursive:是否递归的去查找,false 只在子集里找, true 递归查找
- text:标签的文本信息
- class_:标签的class属性
1 | tag_list = soup.find_all('div) # fing_all 查找所有div 返回一个list 参数名同find |
- 通过标签名查找
- soup.select(‘a’)
- soup.select(‘title’)
- 通过类名查找
- soup.select(‘.sister’)
- 通过 id 名查找
- soup.select(‘#link1’)
- 组合查找
- soup.select(‘p #link1’)
- 直接子标签查找
- soup.select(“head > title”)
- 属性查找
- soup.select(‘a[class=”sister”]’)
- soup.select(‘a[href=”http://example.com/elsie"]')
- soup.select(‘p a[href=”http://example.com/elsie"]')
其他方法
- tag.text # 返回标签的文本
- tag.string # 返回标签的内容
- tag.stripped_string # 递归循环获取内部所有标签文本
- tag.name # 返回标签名称
- tag.attrs # 返回标签的属性 字典类型
- tag.clear() # 清空内部所有标签
- tag.decompose() # 清空所有标签包含自己
- tag.extract() # 同decompose 但是会返回删除的标签
- tag.encode() # 返回标签的字节类型
- tag.decode() # 返回标签的字符串类型
- tag.encode_contents() # 返回标签的内部内容字节类型
- tag.decode_contents() # 返回标签的内部内容字符串类型
- tag.attre = {‘id’: ‘hello’} # 重新赋值
- tag.get(‘id’) # 获取标签的id属性
- tag.has_attr(‘id’) # 判断当前标签是否有id这个属性
- tag.get_text() # 获取当前标签的文本
- tag.index(tag,find(‘div’)) # 查找标签在某个标签中的索引位置
- tag.is_empty_element() 判断是否是自闭和标签:
- 如br,hr,input,img,meta,soacer,link,frame,base
1 | # 当前标签的关联标签,同jquery的链式调用 |
find_all( name , attrs , recursive , text , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
- name 参数
- 字符串:soup.find_all(‘b’)
- 正则:soup.find_all(re.compile(“^b”)
- 如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容
- 列表:soup.find_all([“a”, “b”])
- True:soup.find_all(True)
- True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
- 传方法:
1 | # 如果没有合适过滤器,那么还可以定义一个方法, |
- keyword 参数
- soup.find_all(id=’link2’)
- soup.find_all(href=re.compile(“elsie”))
- soup.find_all(href=re.compile(“elsie”), id=’link1’)
- soup.find_all(“a”, class_=”sister”)
- data_soup.find_all(data-foo=”value”)
- data_soup.find_all(attrs={“data-foo”: “value”})
- text 参数
- soup.find_all(text=”Elsie”)
- soup.find_all(text=[“Tillie”, “Elsie”, “Lacie”])
- soup.find_all(text=re.compile(“Dormouse”))
- limit 参数
- soup.find_all(“a”, limit=2)
- recursive 参数: 递归
1 | # 其他的一些find方法,用法和find_all类似 |
如何提高爬虫的并发
当爬取大量类似信息、数据、新闻、图片等信息时,串行的爬行方式会消耗大量的时间,这时可以通过并行的方式来提高爬虫的效率以节省时间。
- 多进程
- 消耗资源太多,资源浪费
- 没有大量的cpu操作,多为io操作
- 使用多线程
- 相对单线程方式每个线程发送请求的等待时间也有资源浪费
- 使用单线程(gevent、twisted、tornado、asyncio)
1 | #!/usr/bin/env python |
IO多路复用(select、poll、epoll)
目标:用于监听多个socket对象是否发生变化
1 | import select |
- r=[],如果r有值了
- r=[sk1,],表示sk1已经获取到响应的内容了,本次请求可以终止了
- r=[sk1,sk2],表示两个socket都获取到响应的内容了
- w=[],如果w有值了
- w=[sk1,],表示sk1这个socket已经连接成功,下一步接收数据
- w=[sk1,sk2],表示两个socket都连接成功了
非阻塞
1 | #!/usr/bin/env python |
异步回调
1 | #!/usr/bin/env python |
史上最牛逼的异步IO模块
1 | import select |
协程
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程。
- 微线程不存在的,认为创造控制程序先执行某段代码,后跳到某处执行某段代码
- 遇到非IO请求切换,性能更低
- 遇到IO请求切换,性能高,能够实现并发(利用IO等待的过程,再去干其他的事)
- 协程拥有自己的寄存器上下文和栈
- 线程切换的时候是保存在cpu的寄存器
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
- 因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合)
- 每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
优点
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 原子操作(atomic operation)是不需要synchronized,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本,一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
浏览器、爬虫发送请求的本质
- 本质是socket客户端
- socket客户端建立tcp连接的过程是阻塞的
- 等待服务器返回内容是阻塞的
- socket客户端遵循HTTP协议
- 通过\r\n分割的格式规范
- 请求相应之后断开连接
- http协议建立在tcp协议之上
- http协议规定了发送的数据格式
- 请求数据用\r\n分割
- 请求头和请求体用\r\n\r\n分割
- 响应头和响应体也是\r\n分割
- 请求相应之后断开连接
- 短连接,无状态
面试题
- 什么是异步
- 体现是回调
- 什么是非阻塞
- 体现是不等待
- 报错就捕捉
- sk.setblocking(False)
- 自定义异步非阻塞模块
- 基于socket设置setblocking和io多路复用
- 爬虫发送http请求本质创建socket对象
- io多路复用循环监听socket是否发生变化,一旦发生变化,我们可以自定义操作(触发某个函数的执行)
- 本质socket+IO多路复用
- http协议是什么
- 如何提高爬虫的并发
- 进程
- 线程
- 利用异步非阻塞模块实现单线程并发请求
- io多路复用的作用
- select,内部循环检测socket是否发生变化,1024个
- poll,没有个数限制
- epoll,回调的方式,没有个数限制