爬虫理论笔记
web请求过程剖析
服务器渲染
服务器直接整合数据和html文件,并返回
客户端渲染
服务器先返回一个html骨架,之后请求再次返回需要的数据,在浏览器客户端整合成页面(页面源代码没有数据)。
通过Network实时查看服务器对请求的返回,可以抓包到数据。
HTTP协议
HTTP协议,Hyper Text Transfer Protocol(超文本传输协议),服务器和浏览器之间的数据交互遵守的就是HTTP协议。
请求:
1 | 请求行 -> 请求方式(get/post) 请求URL地址 协议 |
响应:
1 | 状态行 -> 协议 状态码(404等) |
请求头中的常见重要内容:
1、User-Agent
:请求载体的身份标识(用什么发送的请求)
2、Referer
:防盗链( 这次请求是从哪个页面来的?反爬使用)
3、cookie
:本地字符串数据信息(用户登录信息,反爬的token)
响应头的重要内容:
1、cookie
:本地字符串数据信息(用户登录信息,反爬的token)
2、token
字样的各种反爬和反攻击的字符串
请求方式:
1、GET
:显示提交
2、POST
:隐式提交
Requests
用requests发送请求,用GET的方式发送请求。
1 | import requests |
关于User-Agent的反爬:
有时候网页服务器会检测你的身份标识(User-Agent),来判断你是正常浏览器访问还是通过爬虫手段。可以先正常访问一下浏览器,然后复制正常访问时的身份标识,创建一个字典来伪造身份
1 | headers = { |
这个信息可以通过,检查 -> 网络 -> 查看发送成功的请求,然后查看他的请求头,里面有你的User-Agent信息。
1 | resp = requests.get(Url, headers = headers) |
获取后直接进行替换headers。
指定字符集
request模块默认的编码字符集是UTF-8,对于有些用GBK编码的网站,爬出数据后可能会是乱码,我们可以更改指定的编码字符集:
1 | #resp = requests.get(URL) |
案例:百度搜索框搜索
通过百度等搜索引擎搜索,发送的请求基本都是GET(查看页面请求头可知),那只要在程序中发送GET请求就好了。实现过于简单不赘叙。
案例:百度翻译单词
通过请求调试发现,百度翻译的数据结果来源于sug页面,在Payload的data项下可以看到请求的内容,比如我输入一个dog,data下就会出现kw : dog,这个kw就是keyword。
所以通过修改sug的kw,然后来进行翻译内容爬取:
1 | url = "https://fanyi.baidu.com/sug" |
案例:豆瓣电影排行榜
电影排行榜的渲染,属于上述的客户端渲染,html框架和数据是分开响应的(框架一般不变而排行榜内容可能会变),此时我们需要的电影数据就不是从网页源代码中寻找。
通过抓包,筛选出XHR格式的响应,里面存储着用于和html框架整合的数据。
一个URL中,问号分割网址和参数,问号后面的是参数类型及其数值。在写程序时,对于较长的URL,可以通过重新封装参数来分离参数和网址地址。
(这里放一张图片)
1 | URL = "your website" |
补充:记得在完成请求和响应后,在程序的最后,关掉responds,防止过多的请求挂载着,使得访问服务器堵塞:
1 | resp.close() |
正则匹配Re模块
正则库re
运用python自带的正则库
1 | import re |
设置一个匹配对象,预加载正则表达式
1 | member = re.compile(" ") |
在单引号内写正则表达式。
还可以传入第二个参数,用来指定额外的条件。
1 | member = re.compile(r'', re.I) #忽略大小写 |
有关正则的python方法
search()
找到第一个匹配的字符串并返回一个Match对象
1 | member = re.compile(r'123') |
match
从头开始匹配,类似于正则表达式开头的^。
findall()
找到所有匹配的字符串并返回一个列表
1 | member = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') |
finditer
匹配字符串中所有内容,返回一个迭代器(Match对象组),用for循环一个一个提出出来后.group( )
1 | it = member.finditer(r" ") |
sub()
找到匹配的字符串并用第一个参数替换
1 | name = re.compile('Agent\s\w+') |
从正则匹配出的字符串中提取需要内容:
group()
和groups()
当使用括号分隔正则表达式时,group()
可以指定返回第几部分,groups()
返回一个元组
1 | phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') |
或者,我们可以给每一部分的匹配内容命名,设定一个组别,然后把组名传入给.group("name")
来提取数据。
1 | #格式: (?P<组名>正则式子) |
举个例子:
1 | string = "<div class='西游记'><span id='10010'>中国联通</span></div>" |
python读写JSON数据
读(load):
1 | data = json.load(load_f) |
写(dump):
1 | json.dump(data, dump_f) |
总结如图:
方法 | 作用 |
---|---|
json.dumps() |
将python对象编码成json 字符串 |
json.loads() |
将json 字符串解码成python对象 |
json.dump() |
将python中的对象转化成json 储存到文件中 |
json.load() |
将文件中的json 的格式转化成python对象提取出来 |
提取HTML内嵌的子页面(超链接)
当我们用正则爬取内嵌在html代码里的数据时,有时候要提取出子页面的URL,这些URL一般被放在超链接中,和一串文字相呼应,在html中超链接的格式是这样的:
1 | <a href = 'URL' title = "浮动标题">一串文字</a> |
用a标签来表示超链接。
bs4解析
html标记语言
html作为一种超文本标记语言,大体上有以下两种标签格式:
两端闭合
1 | <标签 属性 = ”属性值“>被标记的内容</标签> |
自带闭合
1 | <标签 /> |
用Beautiful Soup解析数据
安装bs4模块
1 | pip install bs4 |
把页面源代码交给Beautiful Soup进行处理,生成bs对象
1 | page = BeautifulSoup(resp.text, "html.parser") #指定为html解析器 |
从bs对象中查找数据
find(标签, 属性 = 值)
find_all(标签, 属性 = 值)
关键字冲突问题:
当html的属性和python语法的关键字冲突时,可能会出现语法错误,例如:
1 | table = page.find("table", class = "hq_table") #calss是python的关键字 |
通过find和find_all函数,我们可以嵌套寻找标签内的内容,比如先找到A标签的内容,然后再在A标签里面找到B标签的内容,最后通过.text
函数拿到被标签标记的内容
当我们想要A标签里的属性值时,我们可以用get函数获取:
1 | member = A.get('属性') |
X path解析
安装模块和基础解析
x path是在XML文档中搜索内容的一门语言,兼容html(html是xml的一个子集)
1 | pip install lxml |
使用x path模块:
1 | form xpath import etree |
在x path解析里,用层级嵌套来寻找内容。
1 | xml = """ |
由上述例子:
text()
,用该函数提取文本
/
代表层级关系
*
代表通配符,能代表任意节点
//
代表任意后代,即任意层节点
节点索引
1 | <body> |
对于上述的html,当我们想提取其中<li>
标签下的内容时,会一次性提取三个,我们可以用索引来只提取某个。注意:索引从1开始
1 | tree = etree.parse("A.html") |
而当我们想提取某个固定属性值的节点的内容,比如上述例子中的href
为one的节点时:
1 | result = tree.xpath("/body/li[@href = "one"]/a/text()") |
用符号@
加上属性 = 属性值,来索引特定的内容。
@
符号用来代表属性,使用@属性
来拿到属性值
.
句点号在x path解析里代表当前节点,在相对查找时,一般用./
加相对路径。
由此,通过循环提取出href
内容:
1 | li_list = tree.xpath("/body/li") #先查找到所用li节点 |
注:通过检查元素能快速复制x path,右键 -> copy -> copy x path
requests进阶
处理cookie 登录网站
有些网址的数据必须要在登录后才会出现马,想要爬取这些数据,我们就要模拟登录的过程。
登录的过程实际上是从服务器得到一串cookie,之后客户端带着cookie去请求服务器时,服务器就会发送对应cookie的数据到客户端,简化后就是:
1、登录 -> 得到cookie
2、带着cookie -> 请求内容
使用session进行请求,使得第一个请求到第二个请求之间的cookie不会丢失。
1 | #生成一个会话对象 |
除了会话,在network里直接抓包请求头里的cookie,然后用字典的方式传入headers,同样实现了一样的效果,实际上会话操作也是带着有cookie的请求头去请求到data的。
防盗链Referer
防盗链:回溯本次请求的上一级请求是什么
防盗链是为了防止URL的不正常访问顺序存在的,例如URL2中存在防盗链URL1,那么当我们访问URL2时,就会对它进行一个溯源,若发现URL2的访问不源自于URL1,那么就会报错。
应对这个问题,简单的只需要将Referer
的信息传入headers里就好了。
代理
代理:通过第三方的IP去发送请求
提高爬虫效率
多线程
第一种写法:
导入多线程的线程类:
1 | form threading import Thread |
之后就可以开始编写自己的线程子类了。
1 | from threading import Thread |
继承线程类后,改写run函数,将里面改写成要执行的内容。然后start函数提示线程开始工作(只是提醒可以开始工作了,具体工作时间由CPU决定)
在main函数创建一个线程对象,然后和main函数里的print一起工作:
1 | world! |
就会出现这种异步的打印结果,说明多线程成功实现了。
第二种写法:
1 | def func(): |
多进程
1 | from multiprocessing import process |
python里的写法和多线程基本一致。
线程池(和进程池)
线程池:一次性开辟一些线程,直接给线程池提交任务,线程任务的调度交给线程池来完成。
1 | from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor |
开辟一个线程池:
1 | def func(name): |
这里创建了一个50个线程的线程池,提交func
任务,由线程池来进行调度,部分结果如下:
1 | 线程98 |
协程
协程:当程序遇见了IO操作之类的阻塞时,可以选择性的切换到其他任务上。
在单线程的情况下,实现多任务异步操作,进行任务之间的切换实现CPU的无缝工作。
python实现多任务异步
带有async
关键字的函数是一个协程函数,协程对象可以实现多任务异步操作。
1 | import asyncio |
asyncio.run()
:运行协程函数。(一般用来运行最高层级的入口点 “main()” 函数)
await
:用于挂起阻塞的异步调用接口,一般放在协程对象前面
asyncio.wait()
:并发地运行传入的可迭代对象,比如我们传入一个协程函数列表
新版python异步协程方法wait的改动
python3.8后,协程对象要手动包装成task对象,方法如下
1 | tasks = [ |
在上述例子中,要将协程对象fun1封装,然后再放到task列表里。
aiohttp
模块实现异步请求
在之前的程序中,request.get()
是同步操作的请求,要实现异步操作的请求,就要用到aiohttp
模块。
创建一个aiohttp.ClientSession()
对象,相当于requests模块的requests对象。
1 | async def aiodownload(url): |
这个是一个大致的模板,然后将这个异步函数传入URL放到tasks列表里,用asyncio.wait()
启动,再用asyncio.run()
运行就大功告成了。
Selenium
selenium是一款自动化测试工具,可以打开浏览器然后像人一样去操作浏览器,程序员因此可以直接从selenium中直接提取网页数据。
1 | pip install selenium |
然后需要安装浏览器驱动,我的浏览器用的是Edge,所以直接去Edge官网下载浏览器驱动。
下载完后,将压缩包解压到python解释器所在的文件夹。
1 | from selenium.webdriver import Edge |
用这个代码测试一下,如果能正常打开浏览器然后进入百度就说明可以使用了,不过这时候窗口顶部,会出现一行提示受自动化工具控制的字样,这个字样会影响我们爬取资源,之后再解决。
用xpath
提取数据
selenium工具一般使用xpath
来寻找各种元素:
1 | web = Edge() |
导入键盘模块可以模拟键盘输入:
1 | from selenium.webdriver.common.keys import Keys |
窗口切换
1 | #selenium中不会主动切换窗口,需要我们手动切换 |
下拉列表select
1 | from selenium.webdriver.support.select import Select |
除了根据索引切换,还能根据value值和文本text进行切换,如select_by_visible_text()
和select_by_value
。
配置无头不显示浏览器
我们正常在使用selenium的时候会打开一个浏览器窗口,配置一下浏览器能够使窗口不显示。
1 | from selenium.webdriver.edge.options import Options |
拿到页面Elements
Elements是经过数据加载以及js
等执行后,产生的html内容(大部分时不是页面源代码)
1 | print(web.page_source) |