前言 1 2 3 4 5 6 7 8 9 10 复制#!/usr/bin/env python # -*- coding: utf-8 -*- # 以上两行代码表示声明解释器位置,以及本文件用utf-8编码 # 设置方式: # File -> Settings -> Editor -> File and Code Templates -> Python Script -> 右侧设置默认文本 import urllib3 urllib3.disable_warnings() # 消除不必要的警告,和编码错误,这是前置条件不可少
一、抓包工具 一、原理
这类型工具的原理都是通过代理进行抓包的
http就是简单的显示,然后转发
https因为有加密,所以抓包工具没办法直接显示,需要通过安装抓包工具的证书,再客户端和服务器之间增增加一层抓包工具。 对于浏览器来说,抓包工具就是服务器,应用抓包工具的证书进行加解密;对于服务器来说,抓包工具就是客户端,应用服务器的真实证书进行加解密。
二、抓包工具的作用
能够监听数据
能够正常显示数据
能够设置过滤条件,查看特定目标数据,过滤host,协议,类型等等
能够在监听到的数据中进行查找
能够中断请求,修改参数,手动提交
能够自动提交请求,测试服务器限制
三、准备工作 1、浏览器
需要安装代理工具,各个浏览器不同,总之就是可以快速应用代理配置,比系统代理设置简单易用 需要启用插件,chrome勾选启用并且勾选开发者模式
2、防火墙
关闭系统防火墙或第三方防火墙,或者设置允许规则
3、清除系统代理和IE代理 四、charles
1、安装证书,双击证书或在cmd中输入certmgr.msc
2、配置http和https,proxy setting , SSL proxying setting
3、proxy>>recording setting ,进行过滤
3、断点设置,可以观察每一步操作
4、edit,可以编辑后发送
5、repeat和repeat advance
6、模拟慢速网络
7、filter和recording setting
8、map和rewrite
注意事项:
不建议进行host过滤捕捉,有可能遗漏请求
显示乱码,查看raw
显示正方形框,需要去浏览器查看
依照抓包工具的分析编写爬虫流程后,设置代理,捕获自己的爬虫程序的数据库,和正常访问的对比
五、fiddler 1、捕获窗口表格的列说明
#:抓取HTTP Request的序号,从1开始,以此递增
Result:HTTP状态码
Protocol:请求使用的协议,如HTTP/HTTPS/FTP等
Host:请求地址的主机名
URL:请求资源的位置
Body:该请求的大小
Caching:请求的缓存过期时间或者缓存控制值
Content-Type:请求响应的类型
Process:发送此请求的进程:进程ID
Comments:允许用户为此回话添加备注
Custom:允许用户设置自定义值
2、内置命令与断点命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 复制命令 |对应请求项| 介绍 | 示例 内置命令: ? All 问号后边跟一个字符串,可以匹配出包含这个字符串的请求 ?google > Body 大于号后面跟一个数字,可以匹配出请求大小,大于这个数字请求 >1000 < Body 小于号跟大于号相反,匹配出请求大小,小于这个数字的请求 <100 = Result 等于号后面跟数字,可以匹配HTTP返回码 =200 @ Host @后面跟Host,可以匹配域名 @www.baidu.com select Content-Type select后面跟响应类型,可以匹配到相关的类型 select image cls All 清空当前所有请求 cls dump All 将所有请求打包成saz压缩包,保存到“我的文档\Fiddler2\Captures”目录下 dump start All 开始监听请求 start stop All 停止监听请求 stop 断点命令 bpafter All bpafter后边跟一个字符串,表示中断所有包含该字符串的请求 bpafter baidu(输入bpafter解除断点) bpu All 跟bpafter差不多,只不过这个是收到请求了,中断响应 bpu baidu(输入bpu解除断点) bps Result 后面跟状态吗,表示中断所有是这个状态码的请求 bps 200(输入bps解除断点) bpv/bpm HTTP方法 只中断HTTP方法的命令,HTTP方法如POST、GET bpv get(输入bpv解除断点) g/go All 放行所有中断下来的请求 g
3、websocket 二、GET、POST、session基础用法 一、get、option、head等方法
url : 目标网站的地址
params: url的参数部分,接受的是字典 或 ((‘k1’,’v1’),(‘k2’,’v2’)) , 没参数可以忽略
headers: 请求的headers部分,字典类型
cookies: 指定的cookies,一般由requests自己控制
files: 文件上传时,指定的参数
auth: auth效验,一般不使用
timeout: 2种参数,一种是 数字,就是所有的超时, 这种使用最多!一种是 (connect timeout, read timeout) ,2个数值的元组,第一个参数是连接超时的时间,第二个参数是 读取response超时时间
allow_redirects: 302 类似这样的状态,是否允许自动跳转,默认是允许
proxies: 代理参数, {‘http’:’127.0.0.1:8888’}
verify: 是否效验https服务器的证书是否安全,默认是True,但是我们一般都会修改为 False
stream: 流方式获取内容,一般不会使用
cert: 第一种方式: (.pem), 指定本地文件夹中的 pem文件的完整路径
第二种方式: (‘test.cert’, ‘test.key’) ,就是cert文件和key文件的完整路径
2、基本请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 复制r = requests.get('http://httpbin.org/get') print(r.content) # content是获取 bytes print(r.text) # text 是获取str print(r.status_code) # status_code 状态码 print(r.headers) # 获取响应的头内容,字典 print(r.cookies) # 获取响应的 cookie 内容 ,RequestsCookieJar 类似字典 print(r.encoding) # 获取响应的 编码 print(r.url) # 获取响应的 url 地址
二、POST方法 1、参数
url : 目标网站的地址
params: url的参数部分,接受的是字典 或 ((‘k1’,’v1’),(‘k2’,’v2’)) , 没参数可以忽略
data: form表单数据的提交,在request的body部分,形式是 urlencode , k1=v1&k2=v2 , 和json不同时提交
json: json数据提交,在body部分是: {‘k1’:’v2’} ,和 data不同时提交
headers: 请求的headers部分,字典类型
cookies: 指定的cookies,一般由requests自己控制
files: 文件上传时,指定的参数
auth: auth效验,一般不使用
timeout: 2种参数,一种是 数字,就是所有的超时, 这种使用最多!
一种是 (connect timeout, read timeout) ,2个数值的元组,第一个参数是连接超时的时间,第二个参数是 读取response超时时间
allow_redirects: 302 类似这样的状态,是否允许自动跳转,默认是允许
proxies: 代理参数, {‘http’:’127.0.0.1:8888’}
verify: 是否效验https服务器的证书是否安全,默认是True,但是我们一般都会修改为 False
stream: 流方式获取内容,一般不会使用
cert: 第一种方式: (.pem), 指定本地文件夹中的 pem文件的完整路径
第二种方式: (‘test.cert’, ‘test.key’) ,就是cert文件和key文件的完整路径
三、session用法 1、标准的session初始化 1 2 3 4 5 6 7 8 9 10 复制# 使用session,可以统一设置一些共有的参数,而不需要在每一步的get或post中设置,可继承属性 s = requests.session() s.verify = False # 这个不可少,不然访问https请求,会报错 s.trust_env = False # 不是系统自带的配置,代理配置、verify配置 # s.proxies = {'http': '127.0.0.1:8888' , 'https': '127.0.0.1:8888'} s.headers = { 'header1': 'v1', 'header2': 'v2', }
三、编码 一、前言
1 2 3 4 5 6 7 8 9 10 requests 的 response的编码: 在 requests.utils 中的 get_encoding_from_headers 方法 ,进行的编码判断 1、response的headers中,设置了 content-type, 其中必须是包含 text ,但是没有设置 charset,那么response的 encoding会设置为 ISO-8859-1 2、response的headers中,设置了 content-type, 值包含text,并且设置了charset,那么 encoding 就是 这个 charset 的值 3、headers中没有设置 content-type,那么 encoding 就是 UTF-8 eg: r.encoding = 'utf-8'
二、常见编码 四、一般流程 一、步骤 1、抓包
1、清除浏览器缓存
2、有隐身窗口的使用隐身窗口,没有的就打开一个新的标签
3、打开 charles,并且监听
4、浏览器中启用代理
5、在浏览器中输入目标网址,如: https://www.qidian.com/rank/yuepiao
6、在浏览器正常操作,查看到所有需要的信息的网页
7、操作完成后,停止charles的抓包,关闭网页的代理
8、清理charles的抓包数据
9、保存抓包信息
五、算法加密 (原理就是对bs64进行加密)
一、常见算法加密 1、MD5、RSA、DES、3DES 1 2 3 4 5 复制任何加密都是针对的bytes: b'\xB3\X3B' URL编码解码: 用于url的参数提交 中文、特殊字符 转换为 %B3%3B%53。。。。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 复制base64: 是网络上最常见的用于传输8Bit字节码的编码方式之一, Base64就是一种基于64个可打印字符来表示二进制数据的方法,用于在HTTP环境下传递较长的标识信息 后一两位可能有“=” 防君子不防小人,很容易解密 输出为 A-Z、a-z、0-9和"+"、"/" 字符组成的字符串 在urlencode中: _ - 替换 + / 很多时候字符串尾部为 1个或2个 "=" 把3个字节的二进制拼接, 24位, 按6位分割,变成4个字节,每个字节小于64 最后留下1个字节的时候,会在尾部添加 2个 '=' 最后留下2个字节的时候,会在尾部添加 1个 '=' '中' 这个汉字 , utf-8是: b'\xe4\xb8\xad' 转为 二进制: 1110 01 00 1011 1000 10 10 1101 按6个位进行分割4个字节: 111001 001011 100010 101101 在前面补0 : 00111001 00001011 00100010 00101101 4个字节: b'\xe4\xb8\xad\x48' 转为 二进制: 1110 01 00 1011 1000 10 10 1101 0100 1000 按6个位进行分割4个字节: 111001 001011 100010 101101 010010 00 前面4个字节处理掉,剩余尾部: 010010 00 第二个会在前面补0,依然只有2个字节: 010010 00000000 转换为这个字符串的时候,假设前面2个是A0,会自动在尾部不组4个字符,即补2个 '==': A0==
1 2 3 4 5 6 7 8 9 复制MD5: Message-Digest Algorithm 5(摘要算法5) 1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。 2、容易计算:从原数据计算出MD5值很容易。 3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。 4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。 输出为 128 bit, 每4位二进制组合一个十六进制字符,一般输出为 长度 32 个16进制字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 复制DES: 全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法 入口参数有三个:Key、Data、Mode Key为7个字节共56位,是DES算法的工作密钥; Data为8个字节64位,是要被加密或被解密的数据; Mode为DES的工作方式,有两种:加密或解密 3DES(即Triple DES)是DES向AES过渡的加密算法, 使用两个密钥,执行三次DES算法, 加密的过程是加密-解密-加密 解密的过程是解密-加密-解密 pycrypto安装指南:帮助文档(https://www.dlitz.net/software/pycrypto/api/current/) 要先安装VC2015:microsoft visual studio 2015(14) 1、http://blog.csdn.net/a624806998/article/details/78596543 在执行 python setup.py install 之前,运行 set CL=/FI"%VCINSTALLDIR%\\INCLUDE\\stdint.h" %CL% 2、出现ImportError: No module named 'winrandom'错误 处理:修改python3安装目录下的 lib/Crypto/Random/OSRNG/nt.py 文件中找到 import winrandom 修改为 from Crypto.Random.OSRNG import winrandom
1 2 3 4 5 6 7 8 9 复制AES: 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),这个标准用来替代原先的DES AES的区块长度固定为128 比特,密钥长度则可以是128,192或256比特 (16、24和32字节) 大致步骤如下: 1、密钥扩展(KeyExpansion), 2、初始轮(Initial Round), 3、重复轮(Rounds),每一轮又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey, 4、最终轮(Final Round),最终轮没有MixColumns。
1 2 3 4 5 6 7 8 9 复制RSA: 公钥加密算法,一种非对称密码算法 公钥加密,私钥解密 3个参数: rsa_n, rsa_e,message rsa_n, rsa_e 用于生成公钥 message: 需要加密的消息
1 2 3 4 5 6 7 复制还要很多网站有自定义的加密算法,不是通用的算法,处理方式: 1、破解js,写对应的python算法。优点:执行快,缺点:复杂,难度高,有可能随时需要更新 2、selenium 进行浏览器模拟 3、pyexec,下载这个JS,用pyexec调用这个js的方法 有一些参数很复杂,但是你可以尝试不提交,就是提交 "",是有可能通过的
二、常见加密形式 1、遇到’%’号 1 2 3 4 复制parse.quote(s) # 编码 parse.unquote(s) # 解码 urlencode(d) # 将字典转为get参数串
六、文本解析语言 一、Xpath 1、常用节点解析
nodename 选取此节点的所有子节点。
/ 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 . 选取当前节点。 .. 选取当前节点的父节点。
@ 选取属性
: 匹配任何元素节点。@ 匹配任何属性节点。
node() 配任何类型的节点
and 多属性连接 ,或者 | : p[@class=’v1’ and @type=’hidden’] , ‘//p | //b’
text() a[text()=’test’],文本等于
PS:xpath得到的值都是list,需要通过下标来进行获取元素对象
1 2 在 xpath 中获取多个元素的下标,是从1开始,譬如: //div/p[1] 就是指div元素下的第一个p元素
2、实用案列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 复制# lxml为必备库需要下载 from lxml import etree # lxml初始化,方式一 tree = etree.HTML(html_doc) # 会自动补全 html 标签 print(etree.tostring(tree, encoding="utf-8").decode('utf-8')) # lxml初始化,方式二 tree = etree.fromstring(html_doc) print(etree.tostring(tree, encoding="utf-8").decode('utf-8')) # lxml初始化,方式三 tree = etree.XML(html_doc) print(etree.tostring(tree, encoding="utf-8").decode('utf-8')) # 以下为实例 tree = etree.HTML(html_doc) print(tree.xpath('//title')[0].text) # 节点内容 print(tree.xpath('//title')[0].tag) # 节点名 print(tree.xpath('//p[@class="story"]')[1].text) print(etree.tostring(tree.xpath('//title')[0])) # etree.tostring 输出节点全部信息 print(tree.xpath('//title')[0].getparent().tag) # 父节点 print(tree.xpath('//a')[1].get('class')) # 获取属性 print(tree.xpath('//a')[1].attrib) # 所有属性的字典 print(tree.xpath("//text()")) # 所有字符串,列表形式 print(tree.xpath("//text()")[2]) # 所有字符串,列表形式 print(tree.xpath("string()")) # 所有文本,字符串 类型,以单一标签为分界,如 <br/> 多属性 print(tree.xpath('//a[@class="sister" and @id="link2"]')[0].text) print(tree.xpath('//a[@class="sister" and contains(text(), "Tillie")]')[0].text) print(tree.xpath('//a[@class="sister" and text()="Tillie"]')[0].text)
二、正则split 1、基础语法 基础语法连接一
基础语法连接二
字符
描述
\
将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\’ 匹配 “\” 而 “(“ 则匹配 “(“。
^
匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。
$
匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。
*
匹配前面的子表达式零次或多次。例如,zo 能匹配 “z” 以及 “zoo”。 等价于{0,}。
+
匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
?
匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。
{n }
n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n ,}
n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o’。
{n ,m }
m 和 n 均为非负整数,其中n <= m 。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。
?
当该字符紧跟在任何一个其他限制符 (, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。
.
匹配除 “\n” 之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用象 ‘[.\n]’ 的模式。
(pattern )
匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0 …**$9** 属性。要匹配圆括号字符,请使用 ‘(‘ 或 ‘)’。
(?:pattern )
匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (\
) 来组合一个模式的各个部分是很有用。例如, ‘industr(?:y\
ies) 就是一个比 ‘industry\
industries’ 更简略的表达式。
(?=pattern )
正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,’Windows (?=95\
98\
NT\
2000)’ 能匹配 “Windows 2000” 中的 “Windows” ,但不能匹配 “Windows 3.1” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern )
负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95\
98\
NT\
2000)’ 能匹配 “Windows 3.1” 中的 “Windows”,但不能匹配 “Windows 2000” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x \
y
匹配 x 或 y 。例如,’z\
food’ 能匹配 “z” 或 “food”。’(z\
f)ood’ 则匹配 “zood” 或 “food”。
[xyz ]
字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。
[^xyz ]
负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’。
[a-z ]
字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。
[^a-z ]
负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。
\b
匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B
匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\cx
匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
\d
匹配一个数字字符。等价于 [0-9]。
\D
匹配一个非数字字符。等价于 [^0-9]。
\f
匹配一个换页符。等价于 \x0c 和 \cL。
\n
匹配一个换行符。等价于 \x0a 和 \cJ。
\r
匹配一个回车符。等价于 \x0d 和 \cM。
\s
匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S
匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t
匹配一个制表符。等价于 \x09 和 \cI。
\v
匹配一个垂直制表符。等价于 \x0b 和 \cK。
\w
匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9*]’。*
\W
匹配任何非单词字符。等价于 ‘[^A-Za-z0-9]’。
\xn
匹配 n ,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。.
num
匹配 num ,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符。
n
标识一个八进制转义值或一个向后引用。如果 n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
nm
标识一个八进制转义值或一个向后引用。如果 nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 *nm* 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若n 和 m 均为八进制数字 (0-7),则 *nm* 将匹配八进制转义值 nm 。
nml
如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un
匹配 n ,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (©)。
2、实例用法 1 2 3 4 5 6 7 8 9 10 11 12 复制book_auth = re.search(r'class="book-info ".*?<a.*?>(.*?)</a>', text, re.RegexFlag.S|re.RegexFlag.M).group(1) # search(): 对需要的数据进行匹配,匹配到第一个就返回,匹配不到返回None urls = re.findall(r'class="book-mid-info".*?href="(.*?)".*?target="_blank"', text, re.RegexFlag.S | re.RegexFlag.M) # findall(): 获取匹配到的所有数据并返回。返回值是一个列表,不需要使用group() sub(): 将匹配到的数据进行替换 用法:re.sub(r"content",“data”,“text”) content: 正则表达式 data: 正则匹配后需要替换的内容 text: 需要匹配的内容
三、beautifulsoup4简称BS4 1、简介
python用于解析文本得库,支持第三方的解析器,譬如lxml、html5lib
2、用法 1、初始化 1 2 3 4 复制soup = BeautifulSoup(html_doc, features) # features有11种: # fast, html, html.parser, html5, html5lib, lxml, lxml-html, lxml-xml, permissive, strict, xml
bs4.builder._lxml.LXMLTreeBuilderForXML:xml、lxml-xml、也支持fast、xml、permissive
bs4.builder._lxml.LXMLTreeBuilder:默认、lxml、fast、html、lxml-html、permissive # 使用最多的对象
bs4.builder._html5lib.HTML5TreeBuilder:html5、html5lib, 也支持html、permissive
bs4.builder._htmlparser.HTMLParserTreeBuilder:strict、html.parser, 也支持html
TreeBuilder,4个类继承
HTMLTreeBuilder, 继承TreeBuilder,并由LXMLTreeBuilder、HTML5TreeBuilder和HTMLParserTreeBuilder继承
bs4.builder._htmlparser.BeautifulSoupHTMLParser ,继承系统的html.parser.HTMLParser,HTMLParserTreeBuilder 最终也是使用的这个类进行解析
2、差异性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 复制- 如果被解析的HTML文档是标准格式,那么解析器之间没有任何差别 - 文档不规范时,会各自不同的处理 from bs4 import BeautifulSoup text = '<a><b /></a>' soup_test = BeautifulSoup(text, 'lxml') # 会补全 html,body和p print('lxml:') print(soup_test) soup_test = BeautifulSoup(text, 'html.parser') # 会补全 p print('html.parser:') print(soup_test) soup_test = BeautifulSoup(text, 'html5') # 会补全 html,head,body和p print('html5:') print(soup_test) soup_test = BeautifulSoup(text, 'xml') # 会加xml头,忽略错误的标签 print('xml:') print(soup_test)
3、详细用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 复制soup = BeautifulSoup(html_doc, 'lxml') # print(soup.prettify()) print(soup.title) # 标签,包括标签本身 print(soup.title.name) # 标签的名字 s = soup.title.string print(soup.title.string) # 标签的内容, NavigableString 对象 print(soup.title.text) # 标签的内容, str 对象 print(soup.meta) # 标签 print(soup.meta['charset']) # 标签属性 print(soup.meta.parent.name) # 标签的父标签 print(soup.html.parent.name) print(soup.html.parent.parent) body = soup.find('body') print(body.find('div')) ''' 标签对象,可以和字符串一样编码和解码 ''' markup = "<b>\N{SNOWMAN}</b>" snowman_soup = BeautifulSoup(markup, 'html.parser') tag = snowman_soup.b print(tag) print(tag.encode("utf-8")) print(tag.encode("utf-8").decode('utf-8')) print(tag.encode("iso-8859-1")) print(tag.encode("iso-8859-1").decode('iso-8859-1')) print(tag.encode("gbk")) print(tag.encode("gbk").decode('gbk')) text = ''' <a><b>text1</b><c>text2</c> <d>text3</d><e e1='100'/><f f1='101'/><></a> ''' sibling_soup = BeautifulSoup(text, 'lxml') print(sibling_soup.b.next_sibling) # 兄弟节点 print(sibling_soup.c.previous_sibling) # 兄弟节点 print(sibling_soup.c.next_sibling) # 兄弟节点,是 换行符 print(sibling_soup.d.previous_sibling) # 兄弟节点,是 换行符 print(sibling_soup.a.next_element) # 下一个元素,是 <b>text1</b> print(sibling_soup.b.next_element) # 下一个元素,是 text1 print(sibling_soup.b.next_element.next_element) # 下一个元素,是 换行符 print(sibling_soup.d.previous_element) # 上一个元素,是 换行符 print(sibling_soup.f.previous_element) # 上一个元素,是 <e e1='100'/> print('结束') soup = BeautifulSoup(html_doc, 'lxml') print(soup.find_all('meta')) # 查找所有 print(soup.find_all('meta', limit=2)) # 查找所有 print(soup.find('meta', {'charset': 'UTF-8'})) # 查找特定的一个标签,其实也是调用的find_all,不过会在取到一个值后返回 print(soup.find(id="seajsnode")) # 根据id查找特定的一个标签 print(soup.find('p', {"text": '测试的连接'})) print(soup.find('p', text='测试的连接')) # 根据标签内容查找特定的一个标签,不能仅仅有标签内容一个参数 meta = soup.find('meta', {'name': 'robots'}) print(meta) print(meta.find_next_sibling('meta')) # 查找下个符合条件的兄弟节点 print(meta.find_next_siblings('meta')) # 查找所有符合条件的兄弟节点 # print(meta.find_next_sibling('a')) # 查找下个符合条件的兄弟节点 print(meta.find_next('a')) # 查找下个符合条件的节点 print(meta.find_all_next('a')) # 查找所有符合条件的节点 print(soup.find('body').get_text()) # 获取所有文本 print(soup.find('body').get_text('|')) # 获取所有文本,| 是分隔符
3、注意事项
bs4会自动替换 html_doc 中的 为 ,这是html5的写法,html4之前是:
标签未结束的,会自动补全,例如: 会补全为
4、beautifulsoup4 和其他方法的差异
四、css选择器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 复制print(soup.select("title")) # 标签名 print(soup.select("html head title")) # 逐层查找 print(soup.select("body a")) # 不逐层查找 print(soup.select("body > a")) # > 子节点 print(len(soup.select("body > div"))) # > 子节点 print(soup.select("body > div")) # > 子节点 print(soup.select("input ~ p")) # > 兄弟节点 print(soup.select("#pin-nav")) # 通过id print(soup.select("div#pin-nav")) # 通过id print(soup.select('.share-img')) # 通过class print(soup.select('meta[charset="gb2312"]'))
七、selenium自动化工具 一、介绍(重点!!!下载selenium时一定要关闭所有代理)
一个自动化测试工具。 在爬虫中能做什么?
(多用于登录一些难以登录的网页以获取对应的cookies)
官网的说法: Selenium automates browsers. That’s it! What you do with that power is entirely up to you. 浏览器的自动化操作,你想干嘛就干嘛…..就这么简单!
开源易用,支持多种语言 支持大部分主流的浏览器:firefox,chrome,ie,edge,safari,opera,phantomjs等等
官方文档:https://www.seleniumhq.org/doc
二、selenium使用
selenium包括了很多方面,如Selenium IDE 、Selenium Remote Control 、 Selenium Grid 、Selenium WebDriver 爬虫系统主要使用到Selenium WebDriver,可以在本地或远程计算机上驱动浏览器
三、具体使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 复制# 基础用法 # 浏览器的位置,相对路径,使用绝对路径也是可以的 phantomjs_driver_path = 'browser/phantomjs.exe' from selenium import webdriver # 启动驱动,不同的浏览器启用不同的类 driver = webdriver.PhantomJS(phantomjs_driver_path) # driver = webdriver.Chrome(phantomjs_driver_path) # 设置窗口大小 driver.set_window_size(1366, 768) # 页面的加载超时时间 driver.set_page_load_timeout(10) # script脚本的超时时间 driver.set_script_timeout(10) # 访问目标页面 driver.get('https://www.baidu.com') # 下面有3种延时方式的展示,一般实际项目中不会同一个地方用3个延时,选择一个或多个使用 # 绝对延时,等待规定时间后,直接执行后面的代码 time.sleep(1) # 隐性延时,最长是30秒,如果30秒内,资源全部加载完成,那么执行后续的代码, # 30秒内没有加载完成,也会继续执行后续代码 driver.implicitly_wait(30) # 显性等待,等待时长20秒,间隔0.5秒去查询一次,目标元素是否加载完成 # 20秒内加载完成后,执行后续的代码,最长等待20秒,没有加载也会继续执行 # from selenium.webdriver.support.wait import WebDriverWait # from selenium.webdriver.support import expected_conditions as EC # driver.get('https://huilansame.github.io') # locator = (By.LINK_TEXT, 'CSDN') # # 20 秒是最长等待时间, 0.5 秒是间隔轮询时间 # WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located(locator)) # 通过xpath的方式查找 su = driver.find_element_by_xpath('//*[@id="su"]') # print(su.get_attribute('type')) # print(su.get_attribute('id')) # print(su.get_attribute('value')) # print(su.get_attribute('class')) # # 通过标签的id查找 # su = driver.find_element_by_id('su') # # 通过标签的css选择器查找 # su = driver.find_element_by_css_selector('#su') # # 通过class进行查找 # driver.find_element_by_class_name('bg s_btn') # 也是通过标签的xpath,等同于 driver.find_element_by_xpath('//*[@id="su"]') # driver.find_element(By.XPATH, '//*[@id="su"]') # print(driver.title) # print(su.get_attribute('value')) # 保存屏幕 driver.get_screenshot_as_file('screenshot.jpg') # 需要手动退出driver # 切记切记一定退出 driver.quit() 3.2 # sina移动端 ,火狐 from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time #打开浏览器 driver = webdriver.Firefox() # 设置10秒页面超时返回,类似于requests.get()的timeout选项,driver.get()没有timeout选项 # 以前遇到过driver.get(url)一直不返回,但也不报错的问题,这时程序会卡住,设置超时选项能解决这个问题。 driver.set_page_load_timeout(10) # 设置10秒脚本超时时间 driver.set_script_timeout(10) driver.set_window_size(1366, 768) # 访问新浪移动端,没有验证码 driver.get('https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F') WebDriverWait(driver, 30, 1).until(EC.presence_of_element_located((By.XPATH, '//*[@id="loginName"]'))) print(driver.title) time.sleep(1) user = driver.find_element_by_xpath('//*[@id="loginName"]') # 清除当前input元素中的值,需要清除 user.clear() # 在input元素中输入内容 user.send_keys('51508690@qq.com') pwd = driver.find_element_by_xpath('//*[@id="loginPassword"]') pwd.clear() pwd.send_keys('mumu2018') login = driver.find_element_by_xpath('//*[@id="loginAction"]') # 出发这个login元素的click事件 login.click() WebDriverWait(driver, 30, 1).until(EC.presence_of_element_located((By.XPATH, '//p[@data-node="title"]'))) msg = driver.find_element_by_xpath('/html/body/div[1]/div[1]/div[1]/div[2]/a[2]') msg.click() # 需要手动退出driver driver.quit() print('over')
四、注意事项
1、需要手动关闭 driver.quit()
2、并发使用多进程
3、html中有iframe 需要切换到iframe中去:driver.switch_to.frame(driver.find_element_by_id(“topmenuFrame”)) 然后查找iframe下的元素 切换回默认的frame中:driver.switch_to.frame(0)
4、三种等待资源加载完成 4.1、 time.sleep 强制等待,不管资源的实际加载情况,等待指定时长,时间到后直接执行后续代码
4.2、 driver.implicitly_wait(30) 隐性等待,设置一个最长等待时间,如果资源提前加载,则会立即往下执行 注意这个设置为全局设置,对整个driver的所有资源加载都有效,只需要在开始设置一次 弊端:会等待所有资源加载完成
4.3、 显性等待 from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver.get(‘https://huilansame.github.io' ) locator = (By.LINK_TEXT, ‘CSDN’) WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located(locator))
5、execute_script : 执行任意的js脚本
五、phantomjs无界面浏览器
官方文档:http://phantomjs.org/api/webpage/
六、下载并安装对应的浏览器
八、cookie的应用 一、cookie是什么?
Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、 进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。 定义于 RFC2109 和 2965 中的都已废弃,最新取代的规范是 RFC6265[1]。 Cookie其实就是浏览器缓存
二、cookie的生命周期
1、会话cookie:没有设置expires(是个时间戳)的,浏览器(session)关闭后,就自动失效 2、持久cookie:设置了expires的,根据设置的失效时间决定(expires时间是可以进行修改的, 但是很多网站会做防止修改的设置)
三、cookie具有的属性
name:为一个cookie的名称。 value:为一个cookie的值。
domain:为可以访问此cookie的域名,譬如www.baidu.com:baidu.com就是顶级域名 1、非顶级域名,如二级域名或者三级域名,设置的cookie的domain只能为顶级域名或者二级域名或者三级域名本身,不 能设置其他二级域名的cookie,否则cookie无法生成。 passport.baidu.com 这个域名下,你只能设置 domain 为 passport.baidu.com 或 baidu.com , 不能设置 passport1.baidu.com 2、顶级域名只能设置domain为顶级域名,不能设置为二级域名或者三级域名,否则cookie无法生成。 baidu.com 只能设置 domain 为 baidu.com 3、二级域名能读取设置了domain为顶级域名或者自身的cookie,不能读取其他二级域名domain的cookie。 所以要想cookie在多个二级域名中共享,需要设置domain为顶级域名, 这样就可以在所有二级域名里面或者到这个cookie的值了。 4、顶级域名只能获取到domain设置为顶级域名的cookie,其他domain设置为二级域名的无法获取。
path:为可以访问此cookie的页面路径。 比如www.baidu.com/path,path就是/test, path = /test 设置为: www.baidu.com/test, 那么只有/test路径下的页面可以读取此cookie。 如果想所有路径都能访问,就设置 path = /
expires/Max-Age :为此cookie超时时间。若设置其值为一个时间(一个时间戳),那么当到达此时间后,此cookie失效。 不设置的话默认值是Session,意思是cookie会和session一起失效。 当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
Size: 此cookie大小。 http: cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息, 而不能通过document.cookie来访问此cookie。 secure : boolean型,默认为false,设置是否只能通过https来传递此cookie
爬虫重点关注name和value
四、cookie应用 1、基础应用
1、以任何方式,如浏览器、selenium、封包方式等,获得对应的cookies
2、将cookies保存,可以是在内存、文件、数据库等
3、在你想要应用已有的cookie的 项目 中,已各种方式:文件、数据库、网络等,获取到对应的cookie,然后进行设置到请求的headers中的cookie中,接着就可以访问对应的资源了。
实际应用:
1、有几台专门的服务器,进行登录操作,所有账号保存在数据库,
由这些专门登录的服务器进行登录操作,登录成功后,保存cookies到数据库
2、有专门的应用服务器,从数据库读取cookies,进行相应的业务操作这种服务器不处理登录操作
PS:
1、cookie的应用,必须是服务器支持,不同的session可以使用同一个cookie
2、expires是一个客户端和服务器的君子约定,浏览器检测到失效了,就不会读取这个cookie,大部分网站都不会检测这个cookie失效,部分要求严格的网站是会检测的,和服务器时间进行比对,判断是否失效。一般都通过md5设置
3、cookie都是由服务器设置的,你客户端设置没意义,服务器不会进行验证
通过response的headers中set-cookie 设置
2、selenium只会获得当前域名的cookie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 复制# selenium 设置cookie,只会得到当前域名,如果要获取多个子域名下的cookie,需要分别进行访问获取,并且进行合并 例如: driver.get('http://www.baidu.com') cookies = driver.get_cookies() # 把需要获取cookie的域名进行访问,并且将多个域名的cookie进行合并保存 driver.get('http://passport.baidu.com') cookies.extend(driver.get_cookies()) # 应用时,最简单的就是讲 domain 设置为 顶级域名: file = 'baidu_cookies_update.txt' with open(file, 'r') as f: cookies = json.load(f) for cookie in cookies: cookie['domain'] = '.baidu.com' driver.add_cookie(cookie)
九、PyExecJS脚本执行语言 一、介绍
1 2 这个exe可以直接在cmd中通过 cscript.exe name.js
不管任何的第三方js执行库,最终要执行js代码,必须要有一个js引擎
碰到自定义的加密算法,只有2种途径处理(不考虑selenium):
1、就是用python实现,
2、js调用,这个是有局限的,只能调用纯算法的函数,不能牵扯到document的上下文
selenium属于浏览器模拟,不需要理会js等东西的处理,
但是碰上有行为分析的网站,就很可能被察觉为非人类
pyexecjs,在调用纯加密算法的js代码,可以提供很大的便利,直接调用执行,不需要分析算法
动态内容怎么处理,就是页面加载完成后,js通过ajax方式获取的内容
答:对于封包爬虫来说,是否是ajax的,或者是url点击,或者是post提交等等, 都是没有区别的。 都是通过抓包,获取到请求(ajax的请求是post),只要抓到包, 进行模拟,获取所有提交的参数,直接获取或者加密得到 注意只要别漏包就行了。 对于封包爬虫来说, 1、能够抓取到所有的交互请求 2、能够得到所有的交互提交的参数(1是直接获取,2是加密得来) 3、不需要理会这个交互的触发方式
二、实例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 复制import execjs # 最基本的使用,执行js代码 # print(execjs.eval("'red yellow blue'.split(' ')")) # 获取默认的execjs对象,然后通过eval执行js代码 # ctx = execjs.get() # a = ctx.eval("1 + 2") # print(a) # 执行js代码中指定的函数 # ctx = execjs.compile(""" # function add(x, y) { # return x + y; # } # """) # b = ctx.call('add', 1, 2) # print(b) import execjs js = ''' function createGuid() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } function get(){ var guid = createGuid() + createGuid() + "-" + createGuid() + "-" + createGuid() + createGuid() + "-" + createGuid() + createGuid() + createGuid(); //CreateGuid(); return guid; } ''' ctx = execjs.compile(js) b = ctx.call('get') print(b)
十、aiohttp 一、介绍
aiohttp 是一个异步 http 通讯的 服务端和客户端 框架
项目地址:https://github.com/aio-libs/aiohttp 官方网站:https://pypi.python.org/pypi/aiohttp 官方文档:https://aiohttp.readthedocs.io/en/stable/ 官方教程:https://aiohttp.readthedocs.io/en/stable/tutorial.html#aiohttp-tutorial
二、安装
基于3.1.0 1、使用 pip install aiohttp 2、同时可以安装2个配套的 异步库, pip install cchardet:文本识别的库 pip install aiodns: DNS 解析的库
3、还有一些其他异步库,都不是很成熟,如文件操作的
三、使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 复制# 1、async : 协程 函数定义,都是 async def fun_name 这样定义。 # 2、async with: 协程 上下文管理器 函数 调用 async with aiohttp.ClientSession() as client # 3、await: 在 协程函数内容,即 async def 定义的函数内部,调用所有 协程函数都是使用 await async_fun() text = await response.text() # 4、loop loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(*tasks)) loop.close() # 项目中多个loop时,不清楚的情况下,宁愿不调用这个close # 5、aiohttp.ClientSession 参数(重点): cookies : 用于传入指定的cookie,字典 headers: 用于指定所有请求的默认headers trust_env: 是否获取系统的代理设置 方法: get、post、head等 方法的参数: ssl = None proxy = {} allow_redirects = True timeout:超时时间,单位秒。aiohttp的所有io操作,默认都是5分钟的超时时间 参数提交: params data json # 6、ClientResponse 属性: status:200 reason:ok method:get url:页面url cookies:http.cookies.SimpleCookie charset:页面的charset content_type:页面的content_type 方法(记住,都是方法,调用的后面有小括号): read():返回bytes , 等同于 requests中 content text(encoding=None):页面的text, 可以带入编码 json(encoding=None):页面的内容json # 7、asyncio.Semaphore 用于控制并发的数量,不是线程安全的。 定义:sem = Semaphore(10) 调用:with await sem:
四、注意事项:
async 函数中 sleep 只能使用 asyncio.sleep()
loop.close() ,如果是有多个 loop, 要特别注意,别互相弄混
五、aio原理
1、改写原本的阻塞的io 方法 2、改成内核通知的方式,从之前等待,改成类似回调函数,执行io命令之后,挂起本函数,并且执行下一个 task 3、当io事件完成或者异常时,通过系统通知之前注册事件并且挂起的函数,继续执行
六、实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 复制# 简单请求示例 import aiohttp import asyncio import async_timeout async def fetch(session, url): async with async_timeout.timeout(10): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html = await fetch(session, 'http://www.baidu.com') print(html) loop = asyncio.get_event_loop() loop.run_until_complete(main()) # sem 示例 import random import asyncio async def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asyncio.sleep(random.random()) return x + y async def print_sum(x, y, sem): # 这里控制并发的任务数 async with sem: result = await compute(x, y) print("%s + %s = %s" % (x, y, result)) loop = asyncio.get_event_loop() # 控制并发数 sem = asyncio.Semaphore(5) loop.run_until_complete(asyncio.gather(*[print_sum(i, i + 1, sem) for i in range(100)])) loop.close()
十一、验证码 一、什么是验证码?
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
二、验证码类别
一个合格的验证码,是能够隔离大部分的机器人,而不让普通用户感觉到厌烦,客户体验很好。
由多个 数字、符号、字母、汉字 组成,加一些干扰项,如点状、线状干扰图案,扭曲本身内容 还有那种动态的晃来晃去的。 特点:部署简单,技术也最简单 缺点:防护性不高 突破技术: 1、本机自动识别图片,识别率不是很高,代表性的, 如:sunday.dll(针对不同的网站进行调优,识别率达到50%-60%) pytesseract-oct:机器学习的进行识别 2、可以架设服务器做机器学习的方式进行训练,识别验证码 如:各大主流的验证码打码平台,都有这样的实现 3、通过打码平台进行打码,发送图片到打码平台,平台返回图片内容。现在最主流的 4、通过第三方免费的图片识别技术进行验证码识别,如百度识图,google识图。
如回答本网站域名,简单的1+3=? 等这样的问题 特点:部署比较简单,技术也简单 缺点:防护性一般,但是用户体验稍差 突破技术: 1、纯计算的题,或者那种简单选择出现的字符的题,是可以通过图片识别进行验证通过的 2、部分网站,是可以把所有题目穷尽,全部下载到本地,把图片做相应处理, 然后和需要验证的时候的验证码进行匹配,进行识别 3、打码平台
采用极验的:https://account.geetest.com/register,虎嗅 拖动图片或点击元素后,拖动图片,如:极验验证码 无法使用打码平台,因为这个行为验证的原理,就是本地js捕捉你的鼠标事件 如开始移动事件,单位时间移动距离,上下相对位置的偏移,鼠标的运动轨迹, 前后鼠标移动速度的对比,最终鼠标停止移动的坐标等。。。。 特点:部署麻烦,防护性强 缺点:需要好体验的话需要费用 突破技术: 1、浏览器模拟(难度较大),譬如:selenium+phantomjs, 要模拟正常用户的拖动,不能瞬移,不能完全直线,速度不能匀速,得上下轻微移动 2、js破解(难度很大):还原这个完整图片,查找这个缺口坐标,破解js函数,获取必要的计算参数, 去进行模拟js的计算,得出一系列的值,模拟提交
给出一组图片,按要求点击其中一张或者多张,如:12306、新浪注册(九宫格) 特点:部署稍难,防护性一般 缺点:客户体验一般 突破技术: 1、图片识别 2、打码平台,一般需要订制打码服务
通过文字提醒用户点击图中相同字的位置进行验证。 特点:部署稍难 缺点:客户体验差 突破技术: 1、图片识别后,点击目标坐标 2、打码平台,订制服务
通过输入指定手机号码,获取验证码,进行验证。 解决方案:手机打码平台
通过手机发送特定短信到特定号码,进行验证。 解决方案:手机打码平台
三、处理方式
1、使用百度识图、google识图等第三方API识别图片
曾经 12306可以使用百度识图进行识别 曾经 http://cn.docs88.com/也可以识别简单的图片
大部分打码平台都是AI+打码员人工打码结合
3、pytesseract识别图片
1 2 3 4 5 6 7 8 9 10 11 12 复制> # 在使用pytesseract前,需要下载安装tesseract > # 打开pytesseract.py文件, > # 修改参数 tesseract_cmd = r'D:\tesseract\Tesseract-OCR\tesseract.exe' > # 在安装目录的Tesseract-OCR\tessdata文件夹中添加中文语言包 > > from PIL import Image > import pytesseract > > text=pytesseract.image_to_string(Image.open('1.png'), lang='chi_sim') > print(text) >
chi_sim 是中文字符集 eng 是英文字符集
1.1 安装PILLOW,在pycharm或者pip安装 1.2 安装pytesseract,在pycharm或者pip安装 1.3 tesseract安装指南:https://jingyan.baidu.com/article/219f4bf788addfde442d38fe.html 1.4 https://github.com/tesseract-ocr/tessdata 下载训练内容https://github.com/tesseract-ocr/langdata 下载语言数据 1.5 文档:https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc 1.6 需要VC2015
4.1 图片打码平台识别图片 4.2 手机打码平台完成手机验证码
十二、爬虫代理ip的处理 一、什么叫做反封IP处理
爬虫进行爬取的时候,目标服务器有可能对单位时间内爬取频率超过一定数值的IP进行禁止访问的处理, 绕过这种服务器防护手段,稳定的进行爬取,这种绕过的手段就叫反封IP处理
二、如何反封IP 1、降低频率
部分网站虽然有封IP的处理,但是可以降低爬取的频率,测试服务器的检测阀值, 如果在不触及服务器阀值的情况下,爬取速度能够满足我们的应用需求,那么就直接这么处理就好。 优点:简单,稳定 缺点:速度慢,效率低 原理:测试服务器的拒绝服务的阀值(server_test测试) 有部分网站是没有做任何IP限制的,这种网站建议大家不要太狠,一般还是限制一个单位的访问次数,不然服务器会宕机 得到阀值后,不让服务器检测到你异常,不断的增加延时的时间,判断单位时间内的最大访问次数 譬如,你测算出服务器的允许时间是:1秒1次请求,然后你的爬虫任务是5秒完成一次, 设置5个线程(协程)进行网络访问,每个任务计算消耗时间,如果低于5秒了,就延时
这种方式对于数据价值比较加高的应用,是可取的,可以增服务器来提高单位时间内的爬取数据
2、本机宽带拨号或使用拨号VPS
每次拨号后,会获得一个新的IP地址 通过代码或者第三方拨号软件定时拨号,更换机器IP 优点:较简单,成本低 缺点:拨号有时IP重复,需要多次拨号,效率较低 原理就是: 不管服务器的拒绝服务的阈值,遇到被限制了,就进行拨号,更换IP 一个宽带账号,是在一个固定的IP段内的,重复拨号,有可能连续拨到同一个IP(处理方式可以建立一个已使用的IP列表,拨号后进行检测) 严重的时候,会被服务器把IP段给封掉 一般来讲也不会太多的线程处理,过快拨号,会把系统弄宕机
连接了路由的机器,可以通过路由器的接口进行拨号处理
3、代理IP,最常用的方式
使用代理IP,不管是付费的还是免费的,都经常有获取的ip不能使用的情况 3.1、免费代理:使用程序从一些网站上搜索到免费代理IP,进行测试后,维护起来,形成一个IP池,不断地更新 优点:复杂,成本低 缺点:不稳定,效率稍低 原理: 就是在我们的requests包中, session = requests.session() session.proxy = 设置这个代理的ip和端口 代理IP的数据流(其实就是一个数据转发): 发送: 本机发送一个http请求》》请求发送到代理IP的port,代理服务器做了处理后》》代理服务器发送到目标服务器 返回:目标服务器返回应答内容》》应答内容发送到代理服务器》》发送到本机
经过代理后的数据请求,在目标服务器(譬如百度)看来,访问它的机器的IP是代理服务器的IP地址,而不是你的本机IP
爬虫不使用代理软件,直接通过设置proxy进行代理ip的访问
代理IP转发你的http请求,是因为它机器上有代理服务的软件,会解析请求(只支持部分协议数据的解析),获得目标服务器,然后把数据发送过去
3.2维护代理池
我们可以自己维护一个代理池: 1、通过爬虫,爬取 免费代理网站 上爬取免费的 代理IP和端口, 2、爬到的数据可以保存在内存、文件、数据库,保存的过程中需要对爬取到的IP和PORT进行去重 3、对新获取的IP和PORT,进行检测,检测的方式,根据你的当前的系统功能,设置proxy,连接你的业务的目标服务器,看是否可以正常通信,检测可以根据不同的业务系统进行不同的检测,OK的保存到一个可以使用的IP列表中,不OK的,丢弃或者保存到另外一个异常IP列表中
维护的ip池信息 ip port 获取时间 端口是否可用 projectA是否可用 projectB是否可用 是否已经检测过
4、业务系统运行时,从IP池中获取本系统可用的IP和PORT,设置为proxy,进行业务访问 5、当前IP和PORT异常后,丢弃或标志不可使,并重新获取可用的IP和端口
示例: 1台机器不断爬取代理IP和PORT信息,保存到数据库中,通过数据库进行去重, 可以使用获取代理的机器进行IP和PORT的检测,也可以单独开一台机器进行检测, 多台机器运行业务系统,不断从数据库中提取可用的代理IP和PORT
3.2、付费代理:通过网上一些代理IP服务商购买代理IP服务,使用提供的接口获取代理列表就行 优点:简单,稳定,效率高 缺点:成本高 原理: 代理提供商维护一批可用的IP地址,在你提取IP时,发送给你,稳定性根据不同的服务商不同
4、总结
1、手动操作,次数多了一样会被封IP,一般封多长时间,譬如半小时,服务器也怕是误检测 2、反封IP就2种:1、不被检测出来,2、被检测了,更换IP
十三、scrapy框架学习 一、安装
pip install scrapy
二、创建项目
E:\爬虫>scrapy startproject scrapy_Test
三、详解
项目地址:https://github.com/scrapy/scrapy
官方网站:https://scrapy.org/
官方文档:https://docs.scrapy.org/en/latest/
官方教程:https://docs.scrapy.org/en/latest/intro/tutorial.html
1、settings文件配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 复制# 所有的key 都是全部大写! BOT_NAME = 'firstscrapy' # 项目名 一、ROBOTSTXT_OBEY = Ture,是否遵守 robots.txt, 修改为False 二、DEFAULT_REQUEST_HEADERS : 默认的请求headers,需要设置 三、# 开发模式时,把下面的注释取消掉,启用缓存,可以提高调试效率 # 同样的请求,如果 缓存 当中有保存内容的话,不会去进行网络请求,直接从缓存中返回 # 记住:开发环境下启用!!!!部署时一定要注释掉!!! HTTPCACHE_ENABLED = True HTTPCACHE_EXPIRATION_SECS = 0 HTTPCACHE_DIR = 'httpcache' HTTPCACHE_IGNORE_HTTP_CODES = [] HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' ------------------------------------------------------------------------------------------------ PROXIES = [ {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, ] # 代理 ,在downloder middleware 的 代理中间件中 proxy = random.choice(PROXIES) request.meta['proxy'] = "http://%s" % proxy['ip_port'] SPIDER_MIDDLEWARES:爬虫中间层 DOWNLOADER_MIDDLEWARES:下载中间层 ITEM_PIPELINES = {'项目名.pipelines.PipeLine类名': 300,} # 日志管理 LOG_ENABLED 默认: True,启用logging LOG_ENCODING 默认: 'utf-8',logging使用的编码 LOG_FILE 默认: None,在当前目录里创建logging输出文件的文件名,例如:LOG_FILE = 'log.txt' 配置了这个文件,就不会在控制台输出日志了 LOG_LEVEL 默认: 'DEBUG',log的最低级别,共五级: CRITICAL - 严重错误 ERROR - 一般错误 WARNING - 警告信息 INFO - 一般信息 print 属于这个 info DEBUG - 调试信息 LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。 例如,执行 print("hello") ,其将会在Scrapy log中显示 # 并发 ,现在 = 号右边的 value 就是默认值 CONCURRENT_ITEMS = 100 # 并发处理 items 的最大数量 CONCURRENT_REQUESTS = 16 # 并发下载request页面的最大数量 CONCURRENT_REQUESTS_PER_DOMAIN = 8 # 并发下载任何单域的最大数量, baidu.com , sina.cn 各 8 个 CONCURRENT_REQUESTS_PER_IP = 0 # 并发 每个IP 请求的最大数量, DOWNLOAD_DELAY = 0.25 # 单位秒,支持小数,一般都是随机范围: 0.5*DOWNLOAD_DELAY 到 1.5*DOWNLOAD_DELAY 之间 CONCURRENT_REQUESTS_PER_IP 不为0时,这个延时是针对每个IP,而不是每个域
2、爬虫类
继承自scrapy.Spider ,用于构造Request对象给Scheduler
属性:
name:爬虫的名字,必须唯一 ,必须写!
start_urls:爬虫初始爬取的链接列表
custom_setting = {} :
如果这里配置了某个key,那么会覆盖掉 通用的项目settings中的配置项,自定义的setting配置
方法:
start_requests:启动爬虫的时候调用,爬取urls的链接,可以省略
parse:
response到达spider的时候默认调用,如果在Request对象配置了callback函数,则不会调用,这个parse不能修改名称,parse方法可以迭代返回Item或Request对象,如果返回Request对象,则会进行增量爬取
3、items类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 复制# 继承自scrapy.Item 定义: class Product(scrapy.Item): name = scrapy.Field() title = scrapy.Field() 调用: 和dict一样的调用 product = Product(name='Desktop PC', title='pc title') # 像字典一样的使用: print(product['name']) print(product.get('name')) product['title'] = 'new title' 可以这样转换为字典:dict(product)
4、spider的 parse 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 复制1、ItemLoader def parse(self, response): # text = response.text # tree = etree.HTML(text) # product = Product() # product['name'] = tree.xpath('//div[@class="product_name"]')[0].text + tree.xpath('//div[@class="product_title"]')[0].text # product['price'] = tree.xpath('//p[@id="price"]')[0].text # product['stock'] = tree.xpath('//p#stock')[0].text # product['last_updated'] = 'today' # return product l = ItemLoader(item=Product(), response=response) l.add_xpath('name', '//div[@class="product_name"]') l.add_xpath('name', '//div[@class="product_title"]') l.add_xpath('price', '//p[@id="price"]') l.add_css('stock', 'p#stock') l.add_value('last_updated', 'today') # you can also use literal values return l.load_item() 2、selector response.selector.xpath('//span/text()').extract() # 结果是 list,但是一般情况我们都是只取到一个值,如 ['想要的值'], extract_first 取第一个 response.css('title::text') response.css('img').xpath('@src').extract() response.xpath('//div[@id="not-exists"]/text()').extract_first() response.xpath('//div[@id="not-exists"]/text()').extract_first(default='not-found') response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s(.)') 3、meta yield Request(novel_url, self.parse_next, meta={'name':name, 'age':age}) 在 parse_next 中 通过 response.meta['name'] 获取参数 传递参数到下一个 parse_next 函数
5、pipelines 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 复制必须在settings中,添加 ITEM_PIPELINES = { 'first_scrapy.pipelines.FirstScrapyPipeline': 300, # 优先级,数字越小, 优先级越高,越早调用范围 0-1000 } 对象如下: class FirstScrapyPipeline(object): def process_item(self, item, spider): return item 方法: process_item(self, item, spider): 处理item的方法, 必须有的!!! 参数: item (Item object or a dict) : 获取到的item spider (Spider object) : 获取到item的spider 返回: 一个dict或者item 方法: open_spider(self, spider) : 当spider启动时,调用这个方法 参数: spider (Spider object) – 启动的spider 方法: close_spider(self, spider): 当spider关闭时,调用这个方法 参数: spider (Spider object) – 关闭的spider @classmethod from_crawler(cls, crawler) 参数: crawler (Crawler object) – 使用这个pipe的爬虫crawler 4.1 返回 item 的例子: from scrapy.exceptions import DropItem class PricePipeline(object): vat_factor = 1.15 def process_item(self, item, spider): if item['price']: if item['price_excludes_vat']: item['price'] = item['price'] * self.vat_factor return item else: raise DropItem("Missing price in %s" % item) 4.2 写入文件的例子: import json class JsonWriterPipeline(object): def open_spider(self, spider): self.file = open('items.jl', 'w') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): line = json.dumps(dict(item)) + "\n" self.file.write(line) return item 4.3 写入 mongodb 的例子: import pymongo class MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): # 必须在settings中 配置 MONGO_URI 和 MONGO_DATABASE return cls( mongo_uri=crawler.settings.get('MONGO_URI'), # items 是默认值,如果settings当中没有配置 MONGO_DATABASE ,那么 mongo_db = 'items' mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[self.collection_name].insert_one(dict(item)) return item 4.4 网页快照的例子 import scrapy import hashlib from urllib.parse import quote class ScreenshotPipeline(object): """Pipeline that uses Splash to render screenshot of every Scrapy item.""" SPLASH_URL = "http://localhost:8050/render.png?url={}" def process_item(self, item, spider): encoded_item_url = quote(item["url"]) screenshot_url = self.SPLASH_URL.format(encoded_item_url) request = scrapy.Request(screenshot_url) dfd = spider.crawler.engine.download(request, spider) dfd.addBoth(self.return_item, item) return dfd def return_item(self, response, item): if response.status != 200: # Error happened, return item. return item # Save screenshot to file, filename will be hash of url. url = item["url"] url_hash = hashlib.md5(url.encode("utf8")).hexdigest() filename = "{}.png".format(url_hash) with open(filename, "wb") as f: f.write(response.body) # Store filename in item. item["screenshot_filename"] = filename return item 4.5 去重复值的pipe from scrapy.exceptions import DropItem class DuplicatesPipeline(object): def __init__(self): self.ids_seen = set() def process_item(self, item, spider): if item['id'] in self.ids_seen: raise DropItem("Duplicate item found: %s" % item) else: self.ids_seen.add(item['id']) return item
6、运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 复制# 方法1、命令行中运行: 命令行 中 进入到 first_scrapy 目录中,执行: scrapy crawl quotes # 方法2、pycharm 运行 在 项目 根目录 添加 run.py 文件: from first_scrapy.spiders.quotes import QuotesSpider from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings # 获取settings.py模块的设置 settings = get_project_settings() process = CrawlerProcess(settings=settings) # 可以添加多个spider # process.crawl(Spider1) # process.crawl(Spider2) process.crawl(QuotesSpider) # 启动爬虫,会阻塞,直到爬取完成 process.start() # 方法三 、 from scrapy.cmdline import execute #设置工程命令 import sys import os #设置工程路径,在cmd 命令更改路径而执行scrapy命令调试 #获取run文件的父目录,os.path.abspath(__file__) 为__file__文件目录 sys.path.append(os.path.dirname(os.path.abspath(__file__))) execute(["scrapy","crawl","quotes" ])
四、案列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 复制1、在命令行中,切换到 项目目录:譬如,f:\py_study> # 执行命令:scrapy startproject first_scrapy # 将在 f:\py_study 路径下建立一个 first_scrapy 项目文件夹 # 文件夹结构如下: scrapy.cfg # 部署的配置文件,不需要修改 first_scrapy/ __init__.py items.py # 类一定要继承scrapy.Item,定义我们需要的结构化数据,和ORM有点类似,使用相当于dict # first_item = FirstscrapyItem() name = first_item['name'] name1 = first_item.get('name') first_item['name'] = 'lucy' # 可以转换为字典 dict_item = dict(first_item) # 格式是: dict_item = {name:'terry', age:10, sex:'1'} middlewares.py # 中间件,相当于钩子,可以对爬取前后做预处理,如修改请求header,url过滤等,分 downloadermiddleware、spidermiddleware pipelines.py # 数据处理,将items中结构化的数据进行处理 settings.py # 项目配置文件,key=value 的方式,key必须全部大写! # 包括所有的配置,一些公用的常量也可以写在里面 spiders/ # 爬虫模块的目录,负责配置需要爬取的数据和爬取规则,以及解析数据, # 并且把结构化数据,return 到 pipelines 模块处理 __init__.py 2、在 spiders 中新建: quotes.py: import scrapy # 必须继承 scrapy.Spider class QuotesSpider(scrapy.Spider): # 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。 name = "quotes" def start_requests(self): # 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 # 后续的URL则从初始的URL获取到的数据中提取。 urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] for url in urls: # 必须使用 yield # Request 是 scrapy 自定义的类 # callback, 获取到response 之后的 回调函数 yield scrapy.Request(url=url, callback=self.parse) # 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 def parse(self, response): page = response.url.split("/")[-2] filename = 'quotes-%s.html' % page with open(filename, 'wb') as f: f.write(response.body) self.log('Saved file %s' % filename) 或 class BlogSpider(scrapy.Spider): name = 'blogspider' # 允许访问的域名,可以不写 allowed_domains = ['scrapinghub.com'] start_urls = ['https://blog.scrapinghub.com'] def parse(self, response): for title in response.css('h2.entry-title'): yield {'title': title.css('a ::text').extract_first()} for next_page in response.css('div.prev-post > a'): yield response.follow(next_page, self.parse) ''' 补充: 数据流,所有中间件都启动: 1、启动 spiders 2、spiders 包装一个request, 发送到 scheduler 2.1 request进入 scheduler 之前,会先到 scheduler middleware 进行处理, 处理后,再发送给 scheduler 3、scheduler 接收到 request 后,将之放入到一个 队列 4、engine 向 scheduler 申请 request,得到后,将request 发送给 downloader 4.1 request 从 scheduler 出来后,先到 scheduler middeware 进行处理, 再传给 scheduler middeware 4.2 request 从 scheduler middeware 出来后,需要到 downloader middleware 进行处理,再传给 downloader 5、downloader 接收到 request 之后,访问对应的http资源,接收 response 6、downloader 接收到 response 之后,将 response 发送给 spider 6.1 response 从 downloader 出来后,先经过 downloader middleware 处理 6.2 response 从 ownloader middleware 处理后,经过 spider middleware 处理, 再 传给 spider 7、spider 接收到reponse之后,解析内容, 7.1 url 资源,需要再次请求的,继续包装成 request ,继续第二个步骤 7.2 非 url 资源,就是 数据,结构化成 item ,向后传 8、item 从 spider 出来后,经过 spider middleware ,处理后 ,发送给 item pipeline 9、pipeline 接收到 item 后,读取其中的数据,进行数据持久化 '''
十四、scrapy-redis学习 一、简介
scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目的分布式开发和部署
特征:
您可以启动多个spider工程,相互之间共享单个redis的requests队列。最适合广泛的多个域名网站的内容爬取。
爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理程序来共享item的队列,进行item数据持久化处理
Scheduler调度器 + Duplication复制 过滤器,Item Pipeline,基本spider
二、安装
通过pip 安装: pip install scrapy-redis 本文档使用版本: 0.6.8 依赖环境: python 3.6.3 redis 2.10.6 scrapy 1.5.0
redia详解
三、文档
官方文档:https://scrapy-redis.readthedocs.io/en/stable/
源码位置:https://github.com/rmax/scrapy-redis
四、配置文件 配置文件是在 scrapy 的 settings 中进行修改的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 复制#启用Redis调度存储请求队列 SCHEDULER = "scrapy_redis.scheduler.Scheduler" #确保所有的爬虫通过Redis去重 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #默认请求序列化使用的是pickle 但是我们可以更改为其他类似的。该配置2.X的可以用。3.X的不能用 #SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" #不清除Redis队列、这样可以暂停/恢复 爬取 # SCHEDULER_PERSIST = True #使用优先级调度请求队列 (默认使用) #SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' #可选用的其它队列 #SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' #SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' #最大空闲时间防止分布式爬虫因为等待而关闭 #这只有当上面设置的队列类是SpiderQueue或SpiderStack时才有效 #并且当您的蜘蛛首次启动时,也可能会阻止同一时间启动(由于队列为空) #SCHEDULER_IDLE_BEFORE_CLOSE = 10 #将数据项目在redis进行处理 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } #序列化项目管道作为redis Key存储,假如爬虫类的name='first',name这个KEY就是 'first:items' #REDIS_ITEMS_KEY = '%(spider)s:items' #默认使用ScrapyJSONEncoder进行项目序列化 #You can use any importable path to a callable object. #REDIS_ITEMS_SERIALIZER = 'json.dumps' #指定连接到redis时使用的端口和地址(可选) #REDIS_HOST = 'localhost' #REDIS_PORT = 6379 #指定用于连接redis的URL(可选) #如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT #REDIS_URL = 'redis://user:pass@hostname:9001' #自定义的redis参数(连接超时之类的) #REDIS_PARAMS = {} #自定义redis客户端类 #REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' #如果为True,则使用redis的'spop'进行操作。 #如果需要避免起始网址列表出现重复,这个选项非常有用。开启此选项urls必须通过sadd添加,否则会出现类型错误。 #REDIS_START_URLS_AS_SET = False #RedisSpider和RedisCrawlSpider默认 start_urls 键 #REDIS_START_URLS_KEY = '%(name)s:start_urls' #设置redis使用utf-8之外的编码 #REDIS_ENCODING = 'latin1'
最常用的配置修改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 复制SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" REDIS_URL = 'redis://root:密码@主机IP:端口' ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } SCHEDULER_PERSIST = True
以上常用配置下,会在 redis 中,增加3个key:
list 类型,保存爬虫获取到的 数据item 内容是 json 字符串
set类型,用于爬虫访问的URL去重 内容是 40个字符的 url 的hash 字符串zme
3、 项目名: start_urls (这个一般用不到)
List 类型,用于获取spider启动时爬取的第一个url
zset类型,用于scheduler调度处理 requests 内容是 request 对象的序列化 字符串
scrapy-redis对scrapy项目的改造,只需要增加以上几个配置就可以完成了
[^abc]: