参考 Github 上的教程学习
一个连 python 都没有完全学会的菜鸡来学爬虫
1. 线程
1.1 线程 Pool
1 | from multiprocessing.dummy import Pool |
Pool(5)
五个线程
1.2 所用函数
time.time()
程序当前时间
eg:用来对比单线程和多线程访问 baidu 的速度
2. request 库
2.1 基础用法
1 | url = '' |
2.2 进阶用法
- 使用 requests 模拟发送 get 请求
1 | import requests |
- 使用 requests 模拟发送 post 请求
1 | import requests |
参考学习网站,(异步 GET 与 POST 请求)
3. re 库
3.1 基础用法
1 | re.findall(r'',html,re.S) #返回一个列表,这是一个列表所以可以取第一个数据 |
4. 正则表达式
.*?
5. Xpath—lxml 库
- XPath 是一种查询语言,能从 XML\HTML 的树状结构中寻找节点
5.1 XPath 语法
5.1.1 XPath 语法解析
1 | example_html |
info = selector.xpath('//div[@class="useful"]/ul/li/text()')
就可以提取出 class=”userful”中的三句话,返回一个列表
5.1.2 基本框架
1 | import lxml.html |
5.1.3 example
1 | import lxml.html |
- a.XPath 语句格式
info = selector.xpath('一段XPath语句')
中’一段 XPath 语句’的格式
核心思想:XPath 就是写地址
获取文本://标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/..../text()
获取属性值://标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/..../@属性n
其中的[@属性="属性值"]
不是必需的,其作用是帮助过滤相同的标签,无相同标签可省略 - b.标签 1 的选取
标签 1 可以直接从 html 这个最外层的标签开始,一层一层往下找,这个时候,XPath 语句是这样的:/html/body/div[@class="useful"]/ul/li/text()
但是由于前面的’/html/body’是所有 HTML 通用的,而且没有属性,所以可不写,即带属性标签前的标签都可以省略 - c.可以省略的属性
1、本身标签没有属性
2、这个标签所有的属性值相同 - d.XPath 的特殊情况
1、以相同字符串开头标签[starts-with(@属性,"开头字符串")]
1 | <div id="test-1">需要的内容1</div> |
2、属性值包含相同字符串标签[contains(@属性,"相同字符串")]
3、对 XPath 返回的对象执行 XPath
1 | useful = selector.xpath('//div[@class="useful"]') #这里返回一个列表 |
4、不同标签下的文字
1 | import lxml.html |
5.2 XPath 通过 chrome 辅助构造
在一行源码单击右键,选择“Copy”→“Copy XPath”命令
把结果粘贴下来,可以看到如下的 XPath 语句://*[@id="thread_list"]/li[2]/div/div[2]/div[1]/div[1]/a
_其中方括号中的数字,表示这是第几个该标签,但需要注意,这里的数字是从 1 开始_
6. Beautiful Soup4 库(BS4)
_BS4 在某些方面比 XPath 易懂,但是不如 XPath 简洁,而且由于它是使用 Python 开发的,因此速度比 XPath 慢。_
使用 Beautiful Soup4 提取 HTML 内容,一般要经过以下两步。
6.1 bs4 处理步骤
- 1)处理源代码生成 BeautifulSoup 对象。
解析源代码生成 BeautifulSoup 对象,使用以下代码:soup = BeautifulSoup(网页源代码, '解析器')
解析器:
这里的“解析器”,可以使用 html.parser:soup = BeautifulSoup(source, 'html.parser')
如果安装了 lxml,还可以使用 lxml:soup = BeautifulSoup(source, 'lxml')
- 2)使用 findall()或者 find()来查找内容。
`soup.find(class=’属性值’)`
_由于 HTML 中的 class 属性与 Python 的 class 关键字相同,因此为了不产生冲突,BS4 规定,如果遇到要查询 class 的情况,使用“class_”来代替_
6.2 example
1 | import request |
- 其他查找方法
以‘我需要’为开头的信息content = soup.find_all(text = re.compile('我需要'))
对属性值搜素使用正则,即对 iamstrang 属性值搜索
1 | content = soup.find_all(class_=re.compile('iam'))[0] |
7. 异步加载与请求头
7.1 异步加载
_异步加载:一个页面,点击后网址不变,页面改变_
7.1.1 AJAX 技术
- AJAX 是 Asynchronous JavaScript And XML 的首字母缩写,意为异步 JavaScript 与 XML
- 使用 AJAX 技术,可以在不刷新网页的情况下更新网页数据。使用 AJAX 技术的网页,一般会使用 HTML 编写网页的框架。
- 在打开网页的时候,首先加载的是这个框架。剩下的部分将会在框架加载完成以后再通过 JavaScript 从后台加载。
7.1.2 JSON
- JSON 的全称是 JavaScript Object Notation,是一种轻量级的数据交换格式。网络之间使用 HTTP 方式传递数据的时候,绝大多数情况下传递的都是字符串。
- 因此,当需要把 Python 里面的数据发送给网页或者其他编程语言的时候,可以先将 Python 的数据转化为 JSON 格式的字符串,然后将字符串传递给其他语言,其他语言再将 JSON 格式的字符串转换为它自己的数据格式
- 列表\字典与字符串相互转化
- _python 中字典 or 列表 与 json 格式字符串的相互转化_
1 | import json |
- str=json.dumps(dict)
- dict=json.loads(str)
7.1.3 异步 GET 与 POST 请求
- 使用异步加载技术的网站,被加载的内容是不能在源代码中找到的。
- 为了解决这个问题,就需要使用 Google Chrome 浏览器的开发者模式。在网页上单击右键,选择“检查”命令,然后定位到“Network”选项卡
- 接下来需要刷新网页。在 Windows 下,按 F5 键或者单击地址栏左边的“刷新”按钮
- 单击“Network”选项卡下面出现的“ajax_1_backend”和“ajax_1_postbackend”,并定位到“Response”选项卡,可以看到这里出现了网页上面的内容
- 再选择“Headers”选项卡,可以看到这个请求使用 GET 方式,发送到http://exercise.kingname.info/ajax_1_backend
- 对于网页中的第 2 条内容,查看“Headers”选项卡,可以看到,这是使用 POST 方式向http://exercise.kingname.info/ajax_1_postbackend 发送请求,并以 JSON 格式提交数据
具体代码实现看request 的进阶用法
7.1.4 特殊的异步加载
- 伪装成异步加载的后端渲染,数据就在源代码里,但却不直接显示出来
- 源代码最下面的 JavaScript 代码,其中有一段:
{"code": "\u884c\u52a8\u4ee3\u53f7\uff1a\u5929\u738b\u76d6\u5730\u864e"}
- 使用 Python 去解析,发现可以得到网页上面的内容
1 | import json |
- 这种假的异步加载页面,其处理思路一般是使用正则表达式从页面中把数据提取出来,然后直接解析
1 | import json |
7.1.5 多次请求的异步加载
- 还有一些网页,显示在页面上的内容要经过多次异步请求才能得到。
- 第 1 个 AJAX 请求返回的是第 2 个请求的参数,第 2 个请求的返回内容又是第 3 个请求的参数,只有得到了上一个请求里面的有用信息,才能发起下一个请求
- 在“Headers”选项卡查看这个 POST 请求的具体参数,在 body 里面发现两个奇怪的参数 secret1 和 secret2
- 尝试修改 secret1 和 secret2,发现 POST 请求无法得到想要的结果
奇怪的参数
1 | name: "xx" |
如果修改这两个参数
1 | import json |
- 打开这个练习页的源代码,在源代码中可以找到 secret_2
1 | <html> |
- 虽然在 POST 参数中,名字是 secret2,而源代码中的名字是 secret_2,不过从值可以看出这就是同一个参数
- 源代码里面没有 secret1,因此就要考虑这个参数是不是来自于另一个异步请求
- 继续在开发者工具中查看其他请求,可以成功找到 secret1,注意,它的名字变为了“code”,但是从值可以看出这就是 secret1
- 不少网站也会使用这种改名字的方式来迷惑爬虫开发者
1 | {code: "kingname is genius.", success: true} |
- 这一条请求就是一个不带任何参数的 GET 请求
- _对于这种多次请求才能得到数据的情况,解决办法就是逐一请求,得到返回结果以后再发起下一个请求。具体到这个例子中,那就是先从源代码里面获得 secret2,再通过 GET 请求得到 secret1,最后使用 secret1 和 secret2 来获取页面上显示的内容_
1 | import json |
7.1.6 基于异步加载的简单登录
- 网站的登录方式有很多种,其中有一种比较简单的方式,就是使用 AJAX 发送请求来进行登录
- 在练习页面中根据输入框中的提示,使用用户名“kingname”和密码“genius”进行登录,登录成功以后弹出提示框
- 对于这种简单的登录功能,可以使用抓取异步加载网页的方式来进行处理
- 在 Chrome 开发者工具中可以发现,当单击“登录”按钮时,网页向后台发送了一条请求
{"code": "kingname is genius", "success": true}
1 | import requests |
- 这就是使用 POST 方式的最简单的 AJAX 请求。使用获取 POST 方式的 AJAX 请求的代码,就能成功获取到登录以后返回的内容
7.2 请求头
7.2.1 请求头的作用
- 使用计算机网页版外卖网站的读者应该会发现这样一个现象:第一次登录外卖网页的时候会让你选择当前所在的商业圈,一旦选定好之后关闭浏览器再打开,网页就会自动定位到先前选择的商业圈
- 又比如,例如携程的网站,使用计算机浏览器打开的时候,页面看起来非常复杂多样
- 同一个网址,使用手机浏览器打开时,网址会自动发生改变,而且得到的页面竟然完全不同
同一个网址,PC 端和手机端页面不同
- Headers 称为请求头,浏览器可以将一些信息通过 Headers 传递给服务器,服务器也可以将一些信息通过 Headers 传递给浏览器,电商网站常常应用的 Cookies 就是 Headers 里面的一个部分
7.2.2 伪造请求头
- 打开练习页,使用 Chrome 的开发者工具监控这个页面的网页请求
- 页面看起来像是发起了一个普通的 GET 方式的异步请求给http://exercise.kingname.info/exercise_headers_backend
- 使用 requests 尝试获取这个网址的返回信息,结果发现失败
- 使用浏览器访问网站的时候,网站可以看到一个名称为 Headers(请求头)的东西
1 | headers = { |
- 为了解决这个问题,就需要给爬虫“换头”。把浏览器的头安装到爬虫的身上,这样网站就不知道谁是谁了
- 要换头,首先就需要知道浏览器的头是什么样的。因此需要在 Chrome 浏览器开发者工具的“Network”选项卡的 Request Headers 里面观察这一次请求的请求头
- 在 requests 里面,设置请求头的参数名称为“headers”,它的值是一个字典
带有请求头的请求,使用 requests 的发送格式为:
1 | html = requests.get(url, headers=字典).content.decode() |
- 代码中的字典就对应了浏览器中的请求头
- 在爬虫里面创建一个字典,将 Chrome 的请求头的内容复制进去,并调整好格式,发起一个带有 Chrome 请求头的爬虫请求,可以发现请求获得成功
- 虽然对于某些网站,在请求头里面只需要设置 User-Agent 就可以正常访问了,但是为了保险起见,还是建议把所有项目都带上,这样可以让爬虫更“像”浏览器
7.3 模拟浏览器
- 练习页面
- _问题:_
- 有一些网站在发起 AJAX 请求的时候,会带上特殊的字符串用于身份验证。这种字符串称为 Token
- 打开练习页面,这个页面在发起 AJAX 请求的时候会在 Headers 中带上一个参数 ReqTime;在 POST 发送的数据中会有一个参数 sum
- 多次刷新页面,可以发现 ReqTime 和 sum 一直在变化
- 不难看出 ReqTime 是精确到毫秒的时间戳,即使使用 Python 生成了一个时间戳,也不能得到网页上面的内容
7.3.1 Selenium 介绍
- 虽然在网页的源代码中无法看到被异步加载的内容,但是在 Chrome 的开发者工具的“Elements”选项卡下却可以看到网页上的内容
7.3.2 selenium 安装
- 安装 selenium
pip install selenium
- 下载 ChromeDriver
7.3.3 selenium 的使用
7.3.3.1 获取源代码
- 将 chromedriver 与代码放在同一个文件夹中以方便代码直接调用
1 | # 初始化selenium |
- 指定了 Selenium 使用 ChromeDriver 来操作 Chrome 解析网页,括号里的参数就是 ChromeDriver 可执行文件的地址
- 如果要使用 PhantomJS,只需要修改第 3 行代码即可:driver = webdriver.PhantomJS(‘./phantomjs’),需要将 PhantomJS 的可执行文件与代码放在一起
- 需要特别提醒的是,如果 chromedriver 与代码不在一起,可以通过绝对路径来指定,例如:driver = webdriver.Chrome(‘/usr/bin/chromedriver’)
- 使用 Windows 的读者可在路径字符串左引号的左边加一个“r”符号,将代码写为:driver = webdriver.Chrome(r’C:\server\chromedriver.exe’)
- 初始化完成以后,就可以使用 Selenium 打开网页了。要打开一个网页只需要一行代码:
driver.get('http://exercise.kingname.info/exercise_advanced_ajax.html')
- 代码运行以后会自动打开一个 Chrome 窗口,并在窗口里面自动进入这个网址对应的页面。一旦被异步加载的内容已经出现在了这个自动打开的 Chrome 窗口中,那么此时使用下列代码:
html = driver.page_source
- 就能得到在 Chrome 开发者工具中出现的 HTML 代码
综合:
1 | from selenium import webdriver |
运行程序会出现以下界面
7.3.3.2 等待信息出现
- 设置了一个 5s 的延迟,这是由于 Selenium 并不会等待网页加载完成再执行后面的代码。它只是向 ChromeDriver 发送了一个命令,让 ChromeDriver 打开某个网页
- 至于网页要开多久,Selenium 并不关心。由于被异步加载的内容会延迟出现,因此需要等待它出现以后再开始抓取
7.3.3.3 在网页中获取元素
_在网页中寻找需要的内容,可以使用类似于 Beautiful Soup4 的语法:_
1 | element = driver.find_element_by_id("passwd-id") #如果有多个符合条件的,返回第1个 |
也可以使用 XPath
1 | element = driver.find_element_by_xpath("//input[@id='passwd-id']") |
1 | from selenium import webdriver |
7.4 实例:乐视爬取视频评论
- _1>分析网站的异步加载请求_
- _2>使用 requests 发送请求_
- 通过使用 Chrome 的开发者工具分析页面的异步加载请求,可以发现评论所在的请求
- 可以使用 Python 来模拟这个请求,从而获取视频的评论信息
在请求的 URL 里面有两个参数:vid 和 pid,这两个参数在网页的源代码里面都可以找到
爬虫首先访问视频页面,通过正则表达式获取 vid 和 pid,并将结果保存到“necessary_info”这个类属性对应的字典中
1 | # 核心代码 |
- 访问评论的接口,用 Python 发起请求,获得评论数据
1 | def get_comment(self): |
- 代码中,提前定义的 self.COMMENT_URL 和 self.HEADERS
1 | # 综合 |
1 | print("网站名:{name}, 地址 {url}".format(name="菜鸟教程", url="www.runoob.com")) |
1 | class AssignValue(object): |
8. 模拟登录与验证码
- 对于一个需要登录才能访问的网站,它的页面在登录前和登录后可能是不一样的
- 如果直接使用 requests 去获取源代码,只能得到登录以前的页面源代码
8.1 模拟登录
- 1.使用 Selenium 操作浏览器登录和使用 Cookies 登录虽然简单粗暴,但是有效
- 2.使用模拟提交表单登录虽然较为麻烦,但可以实现自动化
8.1.1 使用 Selenium 模拟登录
1 | 使用Selenium来进行模拟登录,整个过程非常简单。流程如下。 |
- 程序首先打开知乎的登录页面,然后使用“findelement_by name”分别找到输入账号和密码的两个输入框
- 这两个输入框的 name 属性值分别为“account”(我的是 username)和“password”
- 在 Selenium 中可以使用 send_keys()方法往输入框中输入字符串
- 在输入了密码以后,验证码框就会弹出来。知乎使用的验证码为点击倒立的文字,这种验证码不容易自动化处理,因此在这个地方让爬虫先暂停,手动点击倒立文字
- 爬虫中的 input()语句会阻塞程序,直到在控制台按下 Enter 键,爬虫才会继续运行
8.1.2 使用 Cookies 登录
- _Cookie 是用户使用浏览器访问网站的时候网站存放在浏览器中的一小段数据_
- Cookie 的复数形式 Cookies 用来表示各种各样的 Cookie。它们有些用来记录用户的状态信息;有些用来记录用户的操作行为;还有一些,具有现代网络最重要的功能:记录授权信息——用户是否登录以及用户登录哪个账号
- 为了不让用户每次访问网站都进行登录操作,浏览器会在用户第一次登录成功以后放一段加密的信息在 Cookies 中。下次用户访问,网站先检查 Cookies 有没有这个加密信息,如果有并且合法,那么就跳过登录操作,直接进入登录后的页面
- 通过已经登录的 Cookies,可以让爬虫绕过登录过程,直接进入登录以后的页面
- 在已经登录知乎的情况下,打开 Chrome 的开发者工具,定位到“Network”选项卡,然后刷新网页,在加载的内容中随便选择一项,然后看右侧的数据,从 Request Headers 中可以找到 Cookie
1 | cookie: _zap=56180d87-245a-4b79-83e2-711f4629644e; d_c0="AMAY69ZKzRCPTh5KJj9edoIQ4_BiQS3iqwM=|1581434842"; _xsrf=jzLzeCfZignAw6qDdNqO85UOdCrRcB3C; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1581485103,1581492629,1581492650,1581494278; capsion_ticket="2|1:0|10:1581494284|14:capsion_ticket|44:ZjQyY2FjMmZkZTJmNDJkNGI5NmYxMDNkMzc3MTVlNGI=|e2f4eb7e3652b2f1f3e439d7ff4275e4e15bdfbfbed8ce423dceded2da4235cf"; z_c0="2|1:0|10:1581494646|4:z_c0|92:Mi4xY2R0cUJRQUFBQUFBd0JqcjFrck5FQ1lBQUFCZ0FsVk5kdjh3WHdBMEczY0dBVm5MNUFmV1V4cmtja0p1Rm1kMGtn|560b73b3b5f052f6151d4a02e62f1f645f01ad7826d8c183d7152fb2fcf8456d"; Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49=1581494647; tst=r; KLBRSID=81978cf28cf03c58e07f705c156aa833|1581494650|1581494278 |
- 请注意这里一定是“Request Headers”,不要选成了“Response Headers”
- 只要把这个 Request Headers 的内容通过 requests 提交,就能直接进入登录以后的知乎页面了
- 可以看到,使用 Cookie 来登录网页,不仅可以绕过登录步骤,还可以绕过网站的验证码
- Session,是指一段会话。网站会把每一个会话的 ID(Session ID)保存在浏览器的 Cookies 中用来标识用户的身份
- requests 的 Session 模块可以自动保存网站返回的一些信息
- 其实在前面章节中使用的 requests.get(),在底层还是会先创建一个 Session,然后用 Session 去访问
- 对于 HTTPS 的网站,在 requests 发送请求的时候需要带上 verify=False 这个参数,否则爬虫会报错
- 带上这个参数以后,爬虫依然会报一个警告,这是因为没有 HTTPS 的证书
- 不过这个警告不会影响爬虫的运行结果。对于有强迫症的读者,可以参考相关内容为 requests 设置证书,从而解除这个警告
8.1.3 模拟表单登录
- 这个登录页面多了一个“自动登录”复选框输入用户名 kingname,密码 genius,勾选“自动登录”复选框并单击“登录”按钮,可以看到登录成功后的页面
- 打开 Chrome 的开发者工具并监控登录过程
- 然而,仔细观察会发现登录请求的那个网址只会在“Network”选项卡中存在 1s,然后就消失了
- Network”选项卡下面只剩下登录成功后的页面所发起的各种网络请求
- 这是因为表单登录成功以后会进行页面跳转,相当于开了一个新的网页,于是新的请求就会直接把旧的请求覆盖。为了避免这种情况,需要在 Chrome 的开发者工具的“Network”选项卡中勾选“Preserve log”复选框,再一次登录就可以看到登录过程
- 此时可以看到 Status Code 是 302,说明这里有一个网页跳转,也就证明了之前为什么登录以后看不到登录的请求
- 使用 requests 的 Session 模块来模拟这个登录
1 | import requests |
结果
1 | <html> |
8.2 验证码
8.2.1 肉眼打码
- 对于一次登录就可以长时间使用的情况,只需要识别一次验证码即可
- 这种情况下,与其花时间开发一个自动识别验证码的程序,不如直接肉眼识别
肉眼识别验证码有两种情况,借助浏览器与不借助浏览器
1、借助浏览器
在模拟登录中讲到过 Cookies,通过 Cookies 能实现绕过登录,从而直接访问需要登录的网站。因此,对于需要输入验证码才能进行登录的网站,可以手动在浏览器登录网站,并通过 Chrome 获取 Cookies,然后使用 Cookies 来访问网站
这样就可以实现人工输入一次验证码,然后很长时间不再登录。- 2、不借助浏览器
对于仅仅需要识别图片的验证码,可以使用这种方式——先把验证码下载到本地,然后肉眼去识别并手动输入给爬虫
1 | 手动输入验证码的一般流程如下: |
- _需要注意的是,其中的(2)、(3)、(4)、(5)、(6)步是一气呵成的,是在爬虫运行的时候做的。绝对不能先把爬虫程序关闭,肉眼识别验证码以后再重新运行_
1 | import requests |
结果
1 | 请查看图片,然后输入在这里:1595 |
8.2.2 自动打码
1、Python 图像识别
- 对于验证码识别,Python 也有现成的库来使用
- 开源的 OCR 库 pytesseract 配合图像识别引擎 tesseract,可以用来将图片中的文字转换为文本
- 这种方式在爬虫中的应用并不多见。因为现在大部分的验证码都加上了干扰的纹理,已经很少能用单机版的图片识别方式来识别了。所以如果使用这种方式,只有两种情况:网站的验证码极其简单工整,使用大量的验证码来训练 tesseract
_安装 tesseract_
打开网页下载安装包:https://github.com/tesseract-ocr/tesseract/wiki/Downloads ,在“3rd party Windows exe’s/ installer”下面可以找到.exe 安装包
_安装 Python 库_
pip install Pillow
pip install pytesseract
其中,Pillow 是 Python 中专门用来处理图像的第三方库,pytesseract 是专门用来操作 tesseract 的第三方库
_tesseract 的使用_
1 | tesseract的使用非常简单。 |
1 | # 通过以下代码来实现最简单的图片识别: |
2、打码网站
在线验证码识别的网站,简称打码网站。这些网站有一些是使用深度学习技术识别验证码,有一些是雇佣了很多人来人肉识别验证码
网站提供了接口来实现验证码识别服务。使用打码网站理论上可以识别任何使用输入方式来验证的验证码
1 | 这种打码网站的流程一般是这样的。 |
_使用在线打码_
在百度或者谷歌上面搜索“验证码在线识别”,就可以找到很多提供在线打码的网站。但是由于一般这种打码网站是需要交费才能使用的,所以要注意财产安全
8.3 案例-自动登录果壳网
目标网站
使用模拟登录与验证码识别的技术实现自动登录果壳网。 果壳网的登录界面有验证码,请使用人工或者在线打码的方式识别验证码,并让爬虫登录。登录以后可以正确显示“个人资料设置”界面的源代码
- 涉及的知识点:
- (1)爬虫识别验证码。
- (2)爬虫模拟登录。
来自第八章,需要使用再来深度学习