irpas技术客

Python爬虫开发学习全教程第二版,爆肝十万字【建议收藏】_五包辣条!_python 爬虫开发

未知 1721

大家好,我是辣条。?

上次整理的爬虫教程反响不错,但是还是有小伙伴表示不够细致,今天带来了升级版,全文很长,建议先收藏下来。

目录

一、爬虫基础

爬虫概述

http协议复习

二、requests模块

1. requests模块介绍

2. response响应对象

3. requests模块发送请求

4. requests模块发送post请求

5. 利用requests.session进行状态保持

三、数据提取

数据提取概述

数据提取-jsonpath模块

数据提取-lxml模块

四、selenium的使用

selenium的介绍

selenium提取数据

selenium的其它使用方法

五、抓包与反爬与反爬解决方案

常见的反爬手段和解决思路

验证码处理

chrome浏览器使用方法介绍

JS的解析

六、mongodb数据库

Mongodb的介绍和安装

mongodb的简单使用

Mongodb的的增删改查

mongodb的聚合操作

Mongodb的权限管理

mongodb和python交互

七、scrapy爬虫框架

scrapy的概念和流程

scrapy的入门使用

scrapy数据建模与请求

scrapy模拟登陆

scrapy管道的使用

scrapy中间件的使用

scrapy_redis概念作用和流程

scrapy_redis原理分析并实现断点续爬以及分布式爬虫

scrapy_splash组件的使用

scrapy的日志信息与配置

scrapyd部署scrapy项目

Gerapy

八、appium的使用

利用appium自动控制移动设备并提取数据


一、爬虫基础 爬虫概述

知识点:

了解 爬虫的概念

了解 爬虫的作用

了解 爬虫的分类

掌握 爬虫的流程


1. 爬虫的概念

模拟浏览器,发送请求,获取响应

网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟客户端(主要指浏览器)发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。

原则上,只要是客户端(浏览器)能做的事情,爬虫都能够做

爬虫也只能获取客户端(浏览器)所展示出来的数据


知识点:了解爬虫的概念


2. 爬虫的作用

爬虫在互联网世界中有很多的作用,一句话总结就是抓取网站上的信息。

知识点:了解 爬虫的作用


3. 爬虫的分类

3.1 根据被爬取网站的数量不同,可以分为:

通用爬虫,如 搜索引擎

聚焦爬虫,如12306抢票,或专门抓取某一个(某一类)网站数据

3.2 根据是否以获取数据为目的,可以分为:

功能性爬虫,给你喜欢的明星投票、点赞

数据增量爬虫,比如招聘信息

3.3 根据url地址和对应的页面内容是否改变,数据增量爬虫可以分为:

基于url地址变化、内容也随之变化的数据增量爬虫

url地址不变、内容变化的数据增量爬虫

知识点:了解 爬虫的分类


4. 爬虫的流程

爬虫的基本流程如下所示

获取一个url

向url发送请求,并获取响应(需要http协议)

如果从响应中提取url,则继续发送请求获取响应

如果从响应中提取数据,则将数据进行保存


知识点:掌握 爬虫的流程

http协议复习

知识点

掌握 http以及https的概念和默认端口

掌握 爬虫关注的请求头和响应头

了解 常见的响应状态码

理解 浏览器和爬虫爬取的区别

1. http以及https的概念和区别

HTTPS比HTTP更安全,但是性能更低

HTTP:超文本传输协议,默认端口号是80

超文本:是指超过文本,不仅限于文本;还包括图片、音频、视频等文件

传输协议:是指使用共用约定的固定格式来传递转换成字符串的超文本内容

HTTPS:HTTP + SSL(安全套接字层),即带有安全套接字层的超本文传输协,默认端口号:443

SSL对传输的内容(超文本,也就是请求体或响应体)进行加密

可以打开浏览器访问一个url,右键检查,点击net work,点选一个url,查看http协议的形式


知识点:掌握 http以及https的概念和默认端口


2. 爬虫特别关注的请求头和响应头

2.1 特别关注的请求头字段

爬虫特别关注以下几个请求头字段

Content-Type

Host (主机和端口号)

Connection (链接类型)

Upgrade-Insecure-Requests (升级为HTTPS请求)

User-Agent (浏览器名称)

Referer (页面跳转处)

Cookie (Cookie)

Authorization(用于表示HTTP协议中需要认证资源的认证信息,如前边web课程中用于jwt认证)

加粗的请求头为常用请求头,在服务器被用来进行爬虫识别的频率最高,相较于其余的请求头更为重要,但是这里需要注意的是并不意味这其余的不重要,因为有的网站的运维或者开发人员可能剑走偏锋,会使用一些比较不常见的请求头来进行爬虫的甄别

2.2 特别关注的响应头字段

爬虫只关注一个响应头字段

Set-Cookie (对方服务器设置cookie到用户浏览器的缓存)


知识点:掌握 爬虫关注的请求头和响应头


3. 常见的响应状态码

200:成功

302:跳转,新的url在响应的Location头中给出

303:浏览器对于POST的响应进行重定向至新的url

307:浏览器对于GET的响应重定向至新的url

403:资源不可用;服务器理解客户的请求,但拒绝处理它(没有权限)

404:找不到该页面

500:服务器内部错误

503:服务器由于维护或者负载过重未能应答,在响应中可能可能会携带Retry-After响应头;有可能是因为爬虫频繁访问url,使服务器忽视爬虫的请求,最终返回503响应状态码

学习web知识的时候就已经学过了状态码的相关知识,我们知道这是服务器给我的相关反馈,我们在学习的时候就被教育说应该将真实情况反馈给客户端,但是在爬虫中,可能该站点的开发人员或者运维人员为了阻止数据被爬虫轻易获取,可能在状态码上做手脚,也就是说返回的状态码并不一定就是真实情况,比如:服务器已经识别出你是爬虫,但是为了让你疏忽大意,所以照样返回状态码200,但是响应体重并没有数据。

所有的状态码都不可信,一切以是否从抓包得到的响应中获取到数据为准


知识点:了解 常见的响应状态码


4. 浏览器的运行过程

在回顾完http协议后,我们来了解以下浏览器发送http请求的过程

4.1 http请求的过程

浏览器在拿到域名对应的ip后,先向地址栏中的url发起请求,并获取响应

在返回的响应内容(html)中,会带有css、js、图片等url地址,以及ajax代码,浏览器按照响应内容中的顺序依次发送其他的请求,并获取相应的响应

浏览器每获取一个响应就对展示出的结果进行添加(加载),js,css等内容会修改页面的内容,js也可以重新发送请求,获取响应

从获取第一个响应并在浏览器中展示,直到最终获取全部响应,并在展示的结果中添加内容或修改————这个过程叫做浏览器的渲染

4.2 注意:

但是在爬虫中,爬虫只会请求url地址,对应的拿到url地址对应的响应(该响应的内容可以是html,css,js,图片等)

浏览器渲染出来的页面和爬虫请求的页面很多时候并不一样,是因为爬虫不具备渲染的能力(当然后续课程中我们会借助其它工具或包来帮助爬虫对响应内容进行渲染)

浏览器最终展示的结果是由多个url地址分别发送的多次请求对应的多次响应共同渲染的结果

所以在爬虫中,需要以发送请求的一个url地址对应的响应为准来进行数据的提取


知识点:理解 浏览器展示的结果可以由多次请求对应的多次响应共同渲染出来,而爬虫是一次请求对应一个响应


二、requests模块 requests模块

知识点:

掌握 headers参数的使用

掌握 发送带参数的请求

掌握 headers中携带cookie

掌握 cookies参数的使用

掌握 cookieJar的转换方法

掌握 超时参数timeout的使用

掌握 代理ip参数proxies的使用

掌握 使用verify参数忽略CA证书

掌握 requests模块发送post请求

掌握 利用requests.session进行状态保持


前面我们了解了爬虫的基础知识,接下来我们来学习如何在代码中实现我们的爬虫

1. requests模块介绍

1.1 requests模块的作用:

发送http请求,获取响应数据

1.2 requests模块是一个第三方模块,需要在你的python(虚拟)环境中额外安装

pip/pip3 install requests

1.3 requests模块发送get请求

需求:通过requests向百度首页发送请求,获取该页面的源码

运行下面的代码,观察打印输出的结果

# 1.2.1-简单的代码实现 import requests ? # 目标url url = 'https://·/") ? # 把网页保存为图片,69版本以上的谷歌浏览器将无法使用截图功能 # driver.save_screenshot("itcast.png") ? print(driver.title) # 打印页面的标题 ? # 退出模拟浏览器 driver.quit() # 一定要退出!不退出会有残留进程!

1.2 phantomjs无界面浏览器的运行效果

PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript。

from selenium import webdriver ? # 指定driver的绝对路径 driver = webdriver.PhantomJS(executable_path='/home/worker/Desktop/driver/phantomjs') # driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver') ? # 向一个url发起请求 driver.get("http://·/") ? # 把网页保存为图片 driver.save_screenshot("itcast.png") ? # 退出模拟浏览器 driver.quit() # 一定要退出!不退出会有残留进程!

1.3 观察运行效果

python代码能够自动的调用谷歌浏览或phantomjs无界面浏览器,控制其自动访问网站

1.4 无头浏览器与有头浏览器的使用场景

通常在开发过程中我们需要查看运行过程中的各种情况所以通常使用有头浏览器

在项目完成进行部署的时候,通常平台采用的系统都是服务器版的操作系统,服务器版的操作系统必须使用无头浏览器才能正常运行

2. selenium的作用和工作原理

利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)

webdriver本质是一个web-server,对外提供webapi,其中封装了浏览器的各种功能

不同的浏览器使用各自不同的webdriver


知识点:了解 selenium的工作原理


3. selenium的安装以及简单使用

我们以谷歌浏览器的chromedriver为例

3.1 在python虚拟环境中安装selenium模块

pip/pip3 install selenium

3.2 下载版本符合的webdriver


知识点:了解 selenium以及chromedriver的安装


4. selenium的简单使用

接下来我们就通过代码来模拟百度搜索

import time from selenium import webdriver ? # 通过指定chromedriver的路径来实例化driver对象,chromedriver放在当前目录。 # driver = webdriver.Chrome(executable_path='./chromedriver') # chromedriver已经添加环境变量 driver = webdriver.Chrome() ? # 控制浏览器访问url地址 driver.get("https://·/') ? ret = driver.find_elements_by_tag_name('h2') print(ret[0].text) # ? ret = driver.find_elements_by_link_text('黑马程序员') print(ret[0].get_attribute('href')) ? driver.quit()

知识点:掌握 元素对象的操作方法


selenium的其它使用方法

知识点:

掌握 selenium控制标签页的切换

掌握 selenium控制iframe的切换

掌握 利用selenium获取cookie的方法

掌握 手动实现页面等待

掌握 selenium控制浏览器执行js代码的方法

掌握 selenium开启无界面模式

了解 selenium使用代理ip

了解 selenium替换user-agent


1. selenium标签页的切换

当selenium控制浏览器打开多个标签页时,如何控制浏览器在不同的标签页中进行切换呢?需要我们做以下两步:

获取所有标签页的窗口句柄

利用窗口句柄字切换到句柄指向的标签页

这里的窗口句柄是指:指向标签页对象的标识

具体的方法

# 1. 获取当前所有的标签页的句柄构成的列表 current_windows = driver.window_handles ? # 2. 根据标签页句柄列表索引下标进行切换 driver.switch_to.window(current_windows[0])

参考代码示例:

import time from selenium import webdriver ? driver = webdriver.Chrome() driver.get("https://·/") time.sleep(1) ? js = 'window.scrollTo(0,document.body.scrollHeight)' # js语句 driver.execute_script(js) # 执行js的方法 ? time.sleep(5) driver.quit()

执行js的方法:driver.execute_script(js)


知识点:掌握 selenium控制浏览器执行js代码的方法


5. 页面等待

页面在加载的过程中需要花费时间等待网站服务器的响应,在这个过程中标签元素有可能还没有加载出来,是不可见的,如何处理这种情况呢?

页面等待分类

强制等待介绍

显式等待介绍

隐式等待介绍

手动实现页面等待

5.1 页面等待的分类

首先我们就来了解以下selenium页面等待的分类

强制等待

隐式等待

显式等待

5.2 强制等待(了解)

其实就是time.sleep()

缺点时不智能,设置的时间太短,元素还没有加载出来;设置的时间太长,则会浪费时间

5.3 隐式等待

隐式等待针对的是元素定位,隐式等待设置了一个时间,在一段时间内判断元素是否定位成功,如果完成了,就进行下一步

在设置的时间内没有定位成功,则会报超时加载

示例代码

from selenium import webdriver ? driver = webdriver.Chrome() ? ? driver.implicitly_wait(10) # 隐式等待,最长等20秒 ? ? driver.get('https://·') print(driver.title) driver.quit()

知识点:掌握 selenium开启无界面模式


7. selenium使用代理ip

selenium控制浏览器也是可以使用代理ip的!

使用代理ip的方法

实例化配置对象

options = webdriver.ChromeOptions()

配置对象添加使用代理ip的命令

options.add_argument('--proxy-server=http://202.20.16.82:9527')

实例化带有配置对象的driver对象

driver = webdriver.Chrome('./chromedriver', chrome_options=options)

参考代码如下:

from selenium import webdriver options = webdriver.ChromeOptions() # 创建一个配置对象 options.add_argument('--proxy-server=http://202.20.16.82:9527') # 使用代理ip driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象 driver.get('http://·') print(driver.title) driver.quit()

知识点:了解 selenium使用代理ip


8. selenium替换user-agent

selenium控制谷歌浏览器时,User-Agent默认是谷歌浏览器的,这一小节我们就来学习使用不同的User-Agent

替换user-agent的方法

实例化配置对象

options = webdriver.ChromeOptions()

配置对象添加替换UA的命令

options.add_argument('--user-agent=Mozilla/5.0 HAHA')

实例化带有配置对象的driver对象

driver = webdriver.Chrome('./chromedriver', chrome_options=options)

参考代码如下:

from selenium import webdriver options = webdriver.ChromeOptions() # 创建一个配置对象 options.add_argument('--user-agent=Mozilla/5.0 HAHA') # 替换User-Agent driver = webdriver.Chrome('./chromedriver', chrome_options=options) driver.get('http://·') print(driver.title) driver.quit()

知识点:了解 selenium替换user-agent


五、抓包与反爬与反爬解决方案 常见的反爬手段和解决思路

学习目标

了解 服务器反爬的原因

了解 服务器常反什么样的爬虫

了解 反爬虫领域常见的一些概念

了解 反爬的三个方向

了解 常见基于身份识别进行反爬

了解 常见基于爬虫行为进行反爬

了解 常见基于数据加密进行反爬


1 服务器反爬的原因

爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫)。

三月份爬虫是个什么概念呢?每年的三月份我们会迎接一次爬虫高峰期,有大量的硕士在写论文的时候会选择爬取一些往网站,并进行舆情分析。因为五月份交论文,所以嘛,大家都是读过书的,你们懂的,前期各种DotA,LOL,到了三月份了,来不及了,赶紧抓数据,四月份分析一下,五月份交论文,就是这么个节奏。

公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。

数据可以在非登录状态下直接被查询。如果强制登陆,那么可以通过封杀账号的方式让对方付出代价,这也是很多网站的做法。但是不强制对方登录。那么如果没有反爬虫,对方就可以批量复制的信息,公司竞争力就会大大减少。竞争对手可以抓到数据,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来我们网站,这对我们是不利的。

状告爬虫成功的几率小

爬虫在国内还是个擦边球,就是有可能可以起诉成功,也可能完全无效。所以还是需要用技术手段来做最后的保障。

2 服务器常反什么样的爬虫

十分低级的应届毕业生

应届毕业生的爬虫通常简单粗暴,根本不管服务器压力,加上人数不可预测,很容易把站点弄挂。

十分低级的创业小公司

现在的创业公司越来越多,也不知道是被谁忽悠的然后大家创业了发现不知道干什么好,觉得大数据比较热,就开始做大数据。分析程序全写差不多了,发现自己手头没有数据。怎么办?写爬虫爬啊。于是就有了不计其数的小爬虫,出于公司生死存亡的考虑,不断爬取数据。

不小心写错了没人去停止的失控小爬虫

有些网站已经做了相应的反爬,但是爬虫依然孜孜不倦地爬取。什么意思呢?就是说,他们根本爬不到任何数据,除了httpcode是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。

成型的商业对手

这个是最大的对手,他们有技术,有钱,要什么有什么,如果和你死磕,你就只能硬着头皮和他死磕。

抽风的搜索引擎

大家不要以为搜索引擎都是好人,他们也有抽风的时候,而且一抽风就会导致服务器性能下降,请求量跟网络攻击没什么区别。

3 反爬虫领域常见的一些概念

因为反爬虫暂时是个较新的领域,因此有些定义要自己下:

爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。

反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。

误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。

拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。

资源:机器成本与人力成本的总和。

这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,通常服务器反爬就是让爬虫工程师加班才是王道,机器成本并不是特别值钱。

4 反爬的三个方向

基于身份识别进行反爬

基于爬虫行为进行反爬

基于数据加密进行反爬

5 常见基于身份识别进行反爬

1 通过headers字段来反爬

headers中有很多字段,这些字段都有可能会被对方服务器拿过来进行判断是否为爬虫

1.1 通过headers中的User-Agent字段来反爬

反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置

解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)

1.2 通过referer字段或者是其他字段来反爬

反爬原理:爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法

解决方法:添加referer字段

1.3 通过cookie来反爬

反爬原因:通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬

解决方案:进行模拟登陆,成功获取cookies之后在进行数据爬取

2 通过请求参数来反爬

请求参数的获取方法有很多,向服务器发送请求,很多时候需要携带请求参数,通常服务器端可以通过检查请求参数是否正确来判断是否为爬虫

2.1 通过从html静态文件中获取请求数据(github登录数据)

反爬原因:通过增加获取请求参数的难度进行反爬

解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系

2.2 通过发送请求获取请求数据

反爬原因:通过增加获取请求参数的难度进行反爬

解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系,搞清楚请求参数的来源

2.3 通过js生成请求参数

反爬原理:js生成了请求参数

解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现

2.4 通过验证码来反爬

反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为

解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐

6 常见基于爬虫行为进行反爬

1 基于请求频率或总请求数量

爬虫的行为与普通用户有着明显的区别,爬虫的请求频率与请求次数要远高于普通用户

1.1 通过请求ip/账号单位时间内总请求数量进行反爬

反爬原理:正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫

解决方法:对应的通过购买高质量的ip的方式能够解决问题/购买个多账号

1.2 通过同一ip/账号请求之间的间隔进行反爬

反爬原理:正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定同时时间间隔较短,因此可以用来做反爬

解决方法:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠

1.3 通过对请求ip/账号每天请求次数设置阈值进行反爬

反爬原理:正常的浏览行为,其一天的请求次数是有限的,通常超过某一个值,服务器就会拒绝响应

解决方法:对应的通过购买高质量的ip的方法/多账号,同时设置请求间随机休眠

2 根据爬取行为进行反爬,通常在爬取步骤上做分析

2.1 通过js实现跳转来反爬

反爬原理:js实现页面跳转,无法在源码中获取下一页url

解决方法: 多次抓包获取条状url,分析规律

2.2 通过蜜罐(陷阱)获取爬虫ip(或者代理ip),进行反爬

反爬原理:在爬虫获取链接进行请求的过程中,爬虫会根据正则,xpath,css等方式进行后续链接的提取,此时服务器端可以设置一个陷阱url,会被提取规则获取,但是正常用户无法获取,这样就能有效的区分爬虫和正常用户

解决方法: 完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱

2.3 通过假数据反爬

反爬原理:向返回的响应中添加假数据污染数据库,通常家属剧不会被正常用户看到

解决方法: 长期运行,核对数据库中数据同实际页面中数据对应情况,如果存在问题/仔细分析响应内容

2.4 阻塞任务队列

反爬原理:通过生成大量垃圾url,从而阻塞任务队列,降低爬虫的实际工作效率

解决方法: 观察运行过程中请求响应状态/仔细分析源码获取垃圾url生成规则,对URL进行过滤

2.5 阻塞网络IO

反爬原理:发送请求获取响应的过程实际上就是下载的过程,在任务队列中混入一个大文件的url,当爬虫在进行该请求时将会占用网络io,如果是有多线程则会占用线程

解决方法: 观察爬虫运行状态/多线程对请求线程计时/发送请求钱

2.6 运维平台综合审计

反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用

解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理

7 常见基于数据加密进行反爬

1 对响应中含有的数据进行特殊化处理

通常的特殊化处理主要指的就是css数据偏移/自定义字体/数据加密/数据图片/特殊编码格式等

1.1 通过自定义字体来反爬 下图来自汽车之家论坛

反爬思路: 使用自有字体文件

解决思路:切换到手机版/解析字体文件进行翻译


1.3 通过js动态生成数据进行反爬

反爬原理:通过js动态生成

解决思路:解析关键js,获得数据生成流程,模拟生成数据

1.4 通过数据图片化反爬

58同城短租

解决思路:通过使用图片解析引擎从图片中解析数据

1.5 通过编码格式进行反爬

反爬原理: 不适用默认编码格式,在获取响应之后通常爬虫使用utf-8格式进行解码,此时解码结果将会是乱码或者报错

解决思路:根据源码进行多格式解码,或者真正的解码格式

小结

掌握 常见的反爬手段、原理以及应对思路

验证码处理

学习目标

了解 验证码的相关知识

掌握 图片识别引擎的使用

了解 常见的打码平台

掌握 通过打码平台处理验证码的方法


1.图片验证码

1.1 什么是图片验证码

验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。

1.2 验证码的作用

防止恶意破解密码、刷票、论坛灌水、刷页。有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上使用验证码是现在很多网站通行的方式(比如招商银行的网上个人银行,百度社区),我们利用比较简易的方式实现了这个功能。虽然登录麻烦一点,但是对网友的密码安全来说这个功能还是很有必要,也很重要。

1.3 图片验证码在爬虫中的使用场景

注册

登录

频繁发送请求时,服务器弹出验证码进行验证

1.4 图片验证码的处理方案

手动输入(input) 这种方法仅限于登录一次就可持续使用的情况

图像识别引擎解析 使用光学识别引擎处理图片中的数据,目前常用于图片数据提取,较少用于验证码处理

打码平台 爬虫常用的验证码解决方案

2.图片识别引擎

OCR(Optical Character Recognition)是指使用扫描仪或数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件。

2.1 什么是tesseract

Tesseract,一款由HP实验室开发由Google维护的开源OCR引擎,特点是开源,免费,支持多语言,多平台。

2.2 图片识别引擎环境的安装

1 引擎的安装

mac环境下直接执行命令

brew install --with-training-tools tesseract

windows环境下的安装 可以通过exe安装包安装,下载地址可以从GitHub项目中的wiki找到。安装完成后记得将Tesseract 执行文件的目录加入到PATH中,方便后续调用。

linux环境下的安装

sudo apt-get install tesseract-ocr

2 Python库的安装

# PIL用于打开图片文件 pip/pip3 install pillow ? # pytesseract模块用于从图片中解析数据 pip/pip3 install pytesseract

2.3 图片识别引擎的使用

通过pytesseract模块的 image_to_string 方法就能将打开的图片文件中的数据提取成字符串数据,具体方法如下

from PIL import Image import pytesseract ? im = Image.open() ? result = pytesseract.image_to_string(im) ? print(result)

2.4 图片识别引擎的使用扩展

其他ocr平台

? 微软Azure 图像识别:https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/ ? 有道智云文字识别:http://aidemo.youdao.com/ocrdemo ? 阿里云图文识别:https://·

提取数据: ????根据网站结构在spider中实现数据采集相关内容

保存数据: ????使用pipeline进行数据后续处理和保存

4. 创建爬虫

通过命令创建出爬虫文件,爬虫文件为主要的代码作业文件,通常一个网站的爬取动作都会在爬虫文件中进行编写。

命令: ????在项目路径下执行: ????scrapy genspider <爬虫名字> <允许爬取的域名>

爬虫名字: 作为爬虫运行时的参数 允许爬取的域名: 为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。

示例:

? cd myspider ? scrapy genspider itcast itcast.cn

生成的目录和文件结果如下:

5. 完善爬虫

在上一步生成出来的爬虫文件中编写指定网站的数据采集操作,实现数据提取

5.1 在/myspider/myspider/spiders/itcast.py中修改内容如下:

import scrapy ? class ItcastSpider(scrapy.Spider): # 继承scrapy.spider # 爬虫名字 ? name = 'itcast' ? # 允许爬取的范围 ? allowed_domains = ['itcast.cn'] ? # 开始爬取的url地址 ? start_urls = ['http://·/channel/teacher.shtml'] ? ? ? # 数据提取的方法,接受下载中间件传过来的response ? def parse(self, response): ? # scrapy的response对象可以直接进行xpath ? names = response.xpath('//div[@class="tea_con"]//li/div/h3/text()') ? print(names) ? ? # 获取具体数据文本的方式如下 ? ? ? # 分组 ? li_list = response.xpath('//div[@class="tea_con"]//li') ? ? ? for li in li_list: ? ? ? # 创建一个数据字典 ? ? ? ? ? item = {} ? ? ? ? ? # 利用scrapy封装好的xpath选择器定位元素,并通过extract()或extract_first()来获取结果 ? ? ? ? ? item['name'] = li.xpath('.//h3/text()').extract_first() # 老师的名字 ? ? ? ? ? item['level'] = li.xpath('.//h4/text()').extract_first() # 老师的级别 ? ? ? ? ? item['text'] = li.xpath('.//p/text()').extract_first() # 老师的介绍 ? ? ? ? ? print(item)

注意:

scrapy.Spider爬虫类中必须有名为parse的解析

如果网站结构层次比较复杂,也可以自定义其他解析函数

在解析函数中提取的url地址如果要发送请求,则必须属于allowed_domains范围内,但是start_urls中的url地址不受这个限制,我们会在后续的课程中学习如何在解析函数中构造发送请求

启动爬虫的时候注意启动的位置,是在项目路径下启动

parse()函数中使用yield返回数据,注意:解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None

5.2 定位元素以及提取数据、属性值的方法

解析并获取scrapy爬虫中的数据: 利用xpath规则字符串进行定位和提取

response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法

额外方法extract():返回一个包含有字符串的列表

额外方法extract_first():返回列表中的第一个字符串,列表为空没有返回None

5.3 response响应对象的常用属性

response.url:当前响应的url地址

response.request.url:当前响应对应的请求的url地址

response.headers:响应头

response.requests.headers:当前响应的请求头

response.body:响应体,也就是html代码,byte类型

response.status:响应状态码

6 保存数据

利用管道pipeline来处理(保存)数据

6.1 在pipelines.py文件中定义对数据的操作

定义一个管道类

重写管道类的process_item方法

process_item方法处理完item之后必须返回给引擎

import json ? class ItcastPipeline(): ? # 爬虫文件中提取数据的方法每yield一次item,就会运行一次 ? # 该方法为固定名称函数 ? def process_item(self, item, spider): ? ? ? print(item) ? ? ? return item

6.2 在settings.py配置启用管道

ITEM_PIPELINES = { ? 'myspider.pipelines.ItcastPipeline': 400 }

配置项中键为使用的管道类,管道类使用.进行分割,第一个为项目目录,第二个为文件,第三个为定义的管道类。 配置项中值为管道的使用顺序,设置的数值约小越优先执行,该值一般设置为1000以内。

7. 运行scrapy

命令:在项目目录下执行scrapy crawl <爬虫名字>

示例:scrapy crawl itcast


小结

scrapy的安装:pip install scrapy

创建scrapy的项目: scrapy startproject myspider

创建scrapy爬虫:在项目目录下执行 scrapy genspider itcast itcast.cn

运行scrapy爬虫:在项目目录下执行 scrapy crawl itcast

解析并获取scrapy爬虫中的数据:

response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法

extract() 返回一个包含有字符串的列表

extract_first() 返回列表中的第一个字符串,列表为空没有返回None

scrapy管道的基本使用:

完善pipelines.py中的process_item函数

在settings.py中设置开启pipeline

response响应对象的常用属性

response.url:当前响应的url地址

response.request.url:当前响应对应的请求的url地址

response.headers:响应头

response.requests.headers:当前响应的请求头

response.body:响应体,也就是html代码,byte类型

response.status:响应状态码

scrapy数据建模与请求

学习目标:

应用 在scrapy项目中进行建模

应用 构造Request对象,并发送请求

应用 利用meta参数在不同的解析函数中传递数据


1. 数据建模

通常在做项目的过程中,在items.py中进行数据建模

1.1 为什么建模

定义item即提前规划好哪些字段需要抓,防止手误,因为定义好之后,在运行过程中,系统会自动检查

配合注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替

使用scrapy的一些特定组件需要Item做支持,如scrapy的ImagesPipeline管道类,百度搜索了解更多

1.2 如何建模

在items.py文件中定义要提取的字段:

class MyspiderItem(scrapy.Item): ? name = scrapy.Field() ? # 讲师的名字 ? title = scrapy.Field() # 讲师的职称 ? desc = scrapy.Field() ? # 讲师的介绍

1.3 如何使用模板类

模板类定义以后需要在爬虫中导入并且实例化,之后的使用方法和使用字典相同

job.py:

from myspider.items import MyspiderItem ? # 导入Item,注意路径 ... ? def parse(self, response) ? ? ? ? item = MyspiderItem() # 实例化后可直接使用 ? ? ? ? item['name'] = node.xpath('./h3/text()').extract_first() ? ? ? item['title'] = node.xpath('./h4/text()').extract_first() ? ? ? item['desc'] = node.xpath('./p/text()').extract_first() ? ? ? ? ? ? ? print(item)

注意:

from myspider.items import MyspiderItem这一行代码中 注意item的正确导入路径,忽略pycharm标记的错误

python中的导入路径要诀:从哪里开始运行,就从哪里开始导入

1.4 开发流程总结

创建项目 scrapy startproject 项目名

明确目标 在items.py文件中进行建模

创建爬虫 3.1 创建爬虫 scrapy genspider 爬虫名 允许的域 3.2 完成爬虫 修改start_urls 检查修改allowed_domains 编写解析方法

保存数据 在pipelines.py文件中定义对数据处理的管道 在settings.py文件中注册启用管道

2. 翻页请求的思路

对于要提取如下图中所有页面上的数据该怎么办?

回顾requests模块是如何实现翻页请求的:

找到下一页的URL地址

调用requests.get(url)

scrapy实现翻页的思路:

找到下一页的url地址

构造url地址的请求对象,传递给引擎

3. 构造Request对象,并发送请求

3.1 实现方法

确定url地址

构造请求,scrapy.Request(url,callback)

callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析

把请求交给引擎:yield scrapy.Request(url,callback)

3.2 网易招聘爬虫

通过爬取网易招聘的页面的招聘信息,学习如何实现翻页请求

思路分析:

获取首页的数据

寻找下一页的地址,进行翻页,获取数据

注意:

可以在settings中设置ROBOTS协议

# False表示忽略网站的robots.txt协议,默认为True ROBOTSTXT_OBEY = False

可以在settings中设置User-Agent:

# scrapy发送的每一个请求的默认UA都是设置的这个User-Agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'

3.3 代码实现

在爬虫文件的parse方法中:

...... # 提取下一页的href next_url = response.xpath('//a[contains(text(),">")]/@href').extract_first() ? # 判断是否是最后一页 if next_url != 'javascript:void(0)': ? ? ? ? # 构造完整url ? ? ? url = 'https://hr.163.com/position/list.do' + next_url ? # 构造scrapy.Request对象,并yield给引擎 # 利用callback参数指定该Request对象之后获取的响应用哪个函数进行解析 ? yield scrapy.Request(url, callback=self.parse) ......

3.4 scrapy.Request的更多参数

scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])

参数解释

中括号里的参数为可选参数

callback:表示当前的url的响应交给哪个函数去处理

meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等

dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动

method:指定POST或GET请求

headers:接收一个字典,其中不包括cookies

cookies:接收一个字典,专门放置cookies

body:接收json字符串,为POST的数据,发送payload_post请求时使用(在下一章节中会介绍post请求)

4. meta参数的使用

meta的作用:meta可以实现数据在不同的解析函数中的传递

在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:

def parse(self,response): ? ... ? yield scrapy.Request(detail_url, callback=self.parse_detail,meta={"item":item}) ... ? def parse_detail(self,response): ? #获取之前传入的item ? item = resposne.meta["item"]

特别注意

meta参数是一个字典

meta字典中有一个固定的键proxy,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍


小结

完善并使用Item数据类:

在items.py中完善要爬取的字段

在爬虫文件中先导入Item

实力化Item对象后,像字典一样直接使用

构造Request对象,并发送请求:

导入scrapy.Request类

在解析函数中提取url

yield scrapy.Request(url, callback=self.parse_detail, meta={})

利用meta参数在不同的解析函数中传递数据:

通过前一个解析函数 yield scrapy.Request(url, callback=self.xxx, meta={}) 来传递meta

在self.xxx函数中 response.meta.get('key', '') 或 response.meta['key'] 的方式取出传递的数据


参考代码

wangyi/spiders/job.py

import scrapy ? ? class JobSpider(scrapy.Spider): ? name = 'job' ? # 2.检查允许的域名 ? allowed_domains = ['163.com'] ? # 1 设置起始的url ? start_urls = ['https://hr.163.com/position/list.do'] ? ? def parse(self, response): ? ? ? # 获取所有的职位节点列表 ? ? ? node_list = response.xpath('//*[@class="position-tb"]/tbody/tr') ? ? ? # print(len(node_list)) ? ? ? ? # 遍历所有的职位节点列表 ? ? ? for num, node in enumerate(node_list): ? ? ? ? ? # 索引为值除2取余为0的才是含有数据的节点,通过判断进行筛选 ? ? ? ? ? if num % 2 == 0: ? ? ? ? ? ? ? item = {} ? ? ? ? ? ? ? ? item['name'] = node.xpath('./td[1]/a/text()').extract_first() ? ? ? ? ? ? ? item['link'] = node.xpath('./td[1]/a/@href').extract_first() ? ? ? ? ? ? ? item['depart'] = node.xpath('./td[2]/text()').extract_first() ? ? ? ? ? ? ? item['category'] = node.xpath('./td[3]/text()').extract_first() ? ? ? ? ? ? ? item['type'] = node.xpath('./td[4]/text()').extract_first() ? ? ? ? ? ? ? item['address'] = node.xpath('./td[5]/text()').extract_first() ? ? ? ? ? ? ? item['num'] = node.xpath('./td[6]/text()').extract_first().strip() ? ? ? ? ? ? ? item['date'] = node.xpath('./td[7]/text()').extract_first() ? ? ? ? ? ? ? yield item ? ? ? ? # 翻页处理 ? ? ? # 获取翻页url ? ? ? part_url = response.xpath('//a[contains(text(),">")]/@href').extract_first() ? ? ? ? # 判断是否为最后一页,如果不是最后一页则进行翻页操作 ? ? ? if part_url != 'javascript:void(0)': ? ? ? ? ? # 拼接完整翻页url ? ? ? ? ? next_url = 'https://hr.163.com/position/list.do' + part_url ? ? ? ? ? ? yield scrapy.Request( ? ? ? ? ? ? ? url=next_url, ? ? ? ? ? ? ? callback=self.parse ? ? ? ? ? )

wangyi/items.py

class WangyiItem(scrapy.Item): # define the fields for your item here like: name = scrapy.Field() link = scrapy.Field() depart = scrapy.Field() category = scrapy.Field() type = scrapy.Field() address = scrapy.Field() num = scrapy.Field() date = scrapy.Field() scrapy模拟登陆

学习目标:

应用 请求对象cookies参数的使用

了解 start_requests函数的作用

应用 构造并发送post请求


1. 回顾之前的模拟登陆的方法

1.1 requests模块是如何实现模拟登陆的?

直接携带cookies请求页面

找url地址,发送post请求存储cookie

1.2 selenium是如何模拟登陆的?

找到对应的input标签,输入文本点击登陆

1.3 scrapy的模拟登陆

直接携带cookies

找url地址,发送post请求存储cookie

2. scrapy携带cookies直接获取需要登陆后的页面

应用场景

cookie过期时间很长,常见于一些不规范的网站

能在cookie过期之前把所有的数据拿到

配合其他程序使用,比如其使用selenium把登陆之后的cookie获取到保存到本地,scrapy发送请求之前先读取本地cookie

2.1 实现:重构scrapy的starte_rquests方法

scrapy中start_url是通过start_requests来进行处理的,其实现代码如下

# 这是源代码 def start_requests(self): ? cls = self.__class__ ? if method_is_overridden(cls, Spider, 'make_requests_from_url'): ? ? ? warnings.warn( ? ? ? ? ? "Spider.make_requests_from_url method is deprecated; it " ? ? ? ? ? "won't be called in future Scrapy releases. Please " ? ? ? ? ? "override Spider.start_requests method instead (see %s.%s)." % ( ? ? ? ? ? ? ? cls.__module__, cls.__name__ ? ? ? ? ? ), ? ? ? ) ? ? ? for url in self.start_urls: ? ? ? ? ? yield self.make_requests_from_url(url) ? else: ? ? ? for url in self.start_urls: ? ? ? ? ? yield Request(url, dont_filter=True)

所以对应的,如果start_url地址中的url是需要登录后才能访问的url地址,则需要重写start_request方法并在其中手动添加上cookie

2.2 携带cookies登陆github

测试账号 noobpythoner zhoudawei123

import scrapy import re ? class Login1Spider(scrapy.Spider): ? ?name = 'login1' ? ?allowed_domains = ['github.com'] ? ?start_urls = ['https://github.com/NoobPythoner'] # 这是一个需要登陆以后才能访问的页面 ? ? ?def start_requests(self): # 重构start_requests方法 ? ? ? ?# 这个cookies_str是抓包获取的 ? ? ? ?cookies_str = '...' # 抓包获取 ? ? ? ?# 将cookies_str转换为cookies_dict ? ? ? ?cookies_dict = {i.split('=')[0]:i.split('=')[1] for i in cookies_str.split('; ')} ? ? ? ?yield scrapy.Request( ? ? ? ? ? ?self.start_urls[0], ? ? ? ? ? ?callback=self.parse, ? ? ? ? ? ?cookies=cookies_dict ? ? ? ) ? ? ?def parse(self, response): # 通过正则表达式匹配用户名来验证是否登陆成功 ? ? ? ?# 正则匹配的是github的用户名 ? ? ? ?result_list = re.findall(r'noobpythoner|NoobPythoner', response.body.decode()) ? ? ? ?print(result_list) ? ? ? ?pass

注意:

scrapy中cookie不能够放在headers中,在构造请求的时候有专门的cookies参数,能够接受字典形式的coookie

在setting中设置ROBOTS协议、USER_AGENT

3. scrapy.Request发送post请求

我们知道可以通过scrapy.Request()指定method、body参数来发送post请求;但是通常使用scrapy.FormRequest()来发送post请求

3.1 发送post请求

注意:scrapy.FormRequest()能够发送表单和ajax请求

3.1.1 思路分析

找到post的url地址:点击登录按钮进行抓包,然后定位url地址为

找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中

否登录成功:通过请求个人主页,观察是否包含用户名

3.1.2 代码实现如下:

import scrapy import re ? class Login2Spider(scrapy.Spider): ? name = 'login2' ? allowed_domains = ['github.com'] ? start_urls = ['https://github.com/login'] ? ? def parse(self, response): ? ? ? authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first() ? ? ? utf8 = response.xpath("//input[@name='utf8']/@value").extract_first() ? ? ? commit = response.xpath("//input[@name='commit']/@value").extract_first() ? ? ? ? ? ? ? #构造POST请求,传递给引擎 ? ? ? yield scrapy.FormRequest( ? ? ? ? ? "https://github.com/session", ? ? ? ? ? formdata={ ? ? ? ? ? ? ? "authenticity_token":authenticity_token, ? ? ? ? ? ? ? "utf8":utf8, ? ? ? ? ? ? ? "commit":commit, ? ? ? ? ? ? ? "login":"noobpythoner", ? ? ? ? ? ? ? "password":"***" ? ? ? ? ? }, ? ? ? ? ? callback=self.parse_login ? ? ? ) ? ? def parse_login(self,response): ? ? ? ret = re.findall(r"noobpythoner|NoobPythoner",response.text) ? ? ? print(ret)

小技巧

在settings.py中通过设置COOKIES_DEBUG=TRUE 能够在终端看到cookie的传递传递过程


小结

start_urls中的url地址是交给start_request处理的,如有必要,可以重写start_request函数

直接携带cookie登陆:cookie只能传递给cookies参数接收

scrapy.Request()发送post请求

scrapy管道的使用

学习目标:

掌握 scrapy管道(pipelines.py)的使用


之前我们在scrapy入门使用一节中学习了管道的基本使用,接下来我们深入的学习scrapy管道的使用

1. pipeline中常用的方法:

process_item(self,item,spider):

管道类中必须有的函数

实现对item数据的处理

必须return item

open_spider(self, spider): 在爬虫开启的时候仅执行一次

close_spider(self, spider): 在爬虫关闭的时候仅执行一次

2. 管道文件的修改

继续完善wangyi爬虫,在pipelines.py代码中完善

import json from pymongo import MongoClient ? class WangyiFilePipeline(object): ? def open_spider(self, spider): # 在爬虫开启的时候仅执行一次 ? ? ? if spider.name == 'itcast': ? ? ? ? ? self.f = open('json.txt', 'a', encoding='utf-8') ? ? def close_spider(self, spider): # 在爬虫关闭的时候仅执行一次 ? ? ? if spider.name == 'itcast': ? ? ? ? ? self.f.close() ? ? def process_item(self, item, spider): ? ? ? if spider.name == 'itcast': ? ? ? ? ? self.f.write(json.dumps(dict(item), ensure_ascii=False, indent=2) + ',\n') ? ? ? # 不return的情况下,另一个权重较低的pipeline将不会获得item ? ? ? return item ? ? class WangyiMongoPipeline(object): ? def open_spider(self, spider): # 在爬虫开启的时候仅执行一次 ? ? ? if spider.name == 'itcast': ? ? ? # 也可以使用isinstanc函数来区分爬虫类: ? ? ? ? ? con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient ? ? ? ? ? self.collection = con.itcast.teachers # 创建数据库名为itcast,集合名为teachers的集合操作对象 ? ? def process_item(self, item, spider): ? ? ? if spider.name == 'itcast': ? ? ? ? ? self.collection.insert(item) ? ? ? ? ? # 此时item对象必须是一个字典,再插入 ? ? ? ? ? # 如果此时item是BaseItem则需要先转换为字典:dict(BaseItem) ? ? ? # 不return的情况下,另一个权重较低的pipeline将不会获得item ? ? ? return item ? 3. 开启管道

在settings.py设置开启pipeline

...... ITEM_PIPELINES = { ? 'myspider.pipelines.ItcastFilePipeline': 400, # 400表示权重 ? 'myspider.pipelines.ItcastMongoPipeline': 500, # 权重值越小,越优先执行! } ......

别忘了开启mongodb数据库 sudo service mongodb start 并在mongodb数据库中查看 mongo

思考:在settings中能够开启多个管道,为什么需要开启多个?

不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分

不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存

同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分

4. pipeline使用注意点

使用之前需要在settings中开启

pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过:权重值小的优先执行

有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值

pipeline中process_item的方法必须有,否则item没有办法接受和处理

process_item方法接受item和spider,其中spider表示当前传递item过来的spider

open_spider(spider) :能够在爬虫开启的时候执行一次

close_spider(spider) :能够在爬虫关闭的时候执行一次

上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接


小结

管道能够实现数据的清洗和保存,能够定义多个管道实现不同的功能,其中有个三个方法

process_item(self,item,spider):实现对item数据的处理

open_spider(self, spider): 在爬虫开启的时候仅执行一次

close_spider(self, spider): 在爬虫关闭的时候仅执行一次

scrapy中间件的使用

学习目标:

应用 scrapy中使用间件使用随机UA的方法

应用 scrapy中使用代理ip的的方法

应用 scrapy与selenium配合使用


1. scrapy中间件的分类和作用

1.1 scrapy中间件的分类

根据scrapy运行流程中所在位置不同分为:

下载中间件

爬虫中间件

1.2 scrapy中间的作用:预处理request和response对象

对header以及cookie进行更换和处理

使用代理ip等

对请求进行定制化操作,

但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中

爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件

2. 下载中间件的使用方法:

接下来我们对腾讯招聘爬虫进行修改完善,通过下载中间件来学习如何使用中间件 编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后在setting中开启

Downloader Middlewares默认的方法:

process_request(self, request, spider):

当每个request通过下载中间件时,该方法被调用。

返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法

返回Response对象:不再请求,把response返回给引擎

返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法

process_response(self, request, response, spider):

当下载器完成http请求,传递响应给引擎的时候调用

返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法

返回Request对象:通过引擎交给调取器继续请求,此时将不通过其他权重低的process_request方法

在settings.py中配置开启中间件,权重值越小越优先执行

3. 定义实现随机User-Agent的下载中间件

3.1 在middlewares.py中完善代码

import random from Tencent.settings import USER_AGENTS_LIST # 注意导入路径,请忽视pycharm的错误提示 ? class UserAgentMiddleware(object): ? def process_request(self, request, spider): ? ? ? user_agent = random.choice(USER_AGENTS_LIST) ? ? ? request.headers['User-Agent'] = user_agent ? ? ? # 不写return ? class CheckUA: ? def process_response(self,request,response,spider): ? ? ? print(request.headers['User-Agent']) ? ? ? return response # 不能少!

3.2 在settings中设置开启自定义的下载中间件,设置方法同管道

DOWNLOADER_MIDDLEWARES = { ? 'Tencent.middlewares.UserAgentMiddleware': 543, # 543是权重值 ? 'Tencent.middlewares.CheckUA': 600, # 先执行543权重的中间件,再执行600的中间件 }

3.3 在settings中添加UA的列表

USER_AGENTS_LIST = [ ? "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", ? "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", ? "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", ? "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", ? "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", ? "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", ? "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", ? "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" ]

运行爬虫观察现象

4. 代理ip的使用

4.1 思路分析

代理添加的位置:request.meta中增加proxy字段

获取一个代理ip,赋值给request.meta['proxy']

代理池中随机选择代理ip

代理ip的webapi发送请求获取一个代理ip

4.2 具体实现

免费代理ip:

class ProxyMiddleware(object): ? def process_request(self,request,spider): ? ? ? # proxies可以在settings.py中,也可以来源于代理ip的webapi ? ? ? # proxy = random.choice(proxies) ? ? ? ? # 免费的会失效,报 111 connection refused 信息!重找一个代理ip再试 ? ? ? proxy = 'https://1.71.188.37:3128' ? ? ? ? request.meta['proxy'] = proxy ? ? ? return None # 可以不写return

收费代理ip:

# 人民币玩家的代码(使用abuyun提供的代理ip) import base64 ? # 代理隧道验证信息 这个是在那个网站上申请的 proxyServer = 'http://proxy.abuyun.com:9010' # 收费的代理ip服务器地址,这里是abuyun proxyUser = 用户名 proxyPass = 密码 proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass) ? class ProxyMiddleware(object): ? def process_request(self, request, spider): ? ? ? # 设置代理 ? ? ? request.meta["proxy"] = proxyServer ? ? ? # 设置认证 ? ? ? request.headers["Proxy-Authorization"] = proxyAuth

4.3 检测代理ip是否可用

在使用了代理ip的情况下可以在下载中间件的process_response()方法中处理代理ip的使用情况,如果该代理ip不能使用可以替换其他代理ip

class ProxyMiddleware(object): ? ...... ? def process_response(self, request, response, spider): ? ? ? if response.status != '200': ? ? ? ? ? request.dont_filter = True # 重新发送的请求对象能够再次进入队列 ? ? ? ? ? return requst

在settings.py中开启该中间件

5. 在中间件中使用selenium

以github登陆为例

5.1 完成爬虫代码

import scrapy ? class Login4Spider(scrapy.Spider): ? name = 'login4' ? allowed_domains = ['github.com'] ? start_urls = ['https://github.com/1596930226'] # 直接对验证的url发送请求 ? ? def parse(self, response): ? ? ? with open('check.html', 'w') as f: ? ? ? ? ? f.write(response.body.decode())

5.2 在middlewares.py中使用selenium

import time from selenium import webdriver ? ? def getCookies(): ? # 使用selenium模拟登陆,获取并返回cookie ? username = input('输入github账号:') ? password = input('输入github密码:') ? options = webdriver.ChromeOptions() ? options.add_argument('--headless') ? options.add_argument('--disable-gpu') ? driver = webdriver.Chrome('/home/worker/Desktop/driver/chromedriver', ? ? ? ? ? ? ? ? ? ? ? ? ? ? chrome_options=options) ? driver.get('https://github.com/login') ? time.sleep(1) ? driver.find_element_by_xpath('//*[@id="login_field"]').send_keys(username) ? time.sleep(1) ? driver.find_element_by_xpath('//*[@id="password"]').send_keys(password) ? time.sleep(1) ? driver.find_element_by_xpath('//*[@id="login"]/form/div[3]/input[3]').click() ? time.sleep(2) ? cookies_dict = {cookie['name']: cookie['value'] for cookie in driver.get_cookies()} ? driver.quit() ? return cookies_dict ? class LoginDownloaderMiddleware(object): ? ? def process_request(self, request, spider): ? ? ? cookies_dict = getCookies() ? ? ? print(cookies_dict) ? ? ? request.cookies = cookies_dict # 对请求对象的cookies属性进行替换

配置文件中设置开启该中间件后,运行爬虫可以在日志信息中看到selenium相关内容


小结

中间件的使用:

完善中间件代码:

process_request(self, request, spider):

当每个request通过下载中间件时,该方法被调用。

返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法

返回Response对象:不再请求,把response返回给引擎

返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法

process_response(self, request, response, spider):

当下载器完成http请求,传递响应给引擎的时候调用

返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法

返回Request对象:通过引擎交给调取器继续请求,此时将不通过其他权重低的process_request方法

需要在settings.py中开启中间件 DOWNLOADER_MIDDLEWARES = { 'myspider.middlewares.UserAgentMiddleware': 543, }

scrapy_redis概念作用和流程

学习目标

了解 分布式的概念及特点

了解 scarpy_redis的概念

了解 scrapy_redis的作用

了解 scrapy_redis的工作流程


在前面scrapy框架中我们已经能够使用框架实现爬虫爬取网站数据,如果当前网站的数据比较庞大, 我们就需要使用分布式来更快的爬取数据

1. 分布式是什么

简单的说 分布式就是不同的节点(服务器,ip不同)共同完成一个任务

2. scrapy_redis的概念

scrapy_redis是scrapy框架的基于redis的分布式组件

3. scrapy_redis的作用

Scrapy_redis在scrapy的基础上实现了更多,更强大的功能,具体体现在:

通过持久化请求队列和请求的指纹集合来实现:

断点续爬

分布式快速抓取

4. scrapy_redis的工作流程

思考:那么,在这个基础上,如果需要实现分布式,即多台服务器同时完成一个爬虫,需要怎么做呢?

4.2 scrapy_redis的流程

在scrapy_redis中,所有的待抓取的request对象和去重的request对象指纹都存在所有的服务器公用的redis中

所有的服务器中的scrapy进程公用同一个redis中的request对象的队列

所有的request对象存入redis前,都会通过该redis中的request指纹集合进行判断,之前是否已经存入过

在默认情况下所有的数据会保存在redis中


小结

scarpy_redis的分布式工作原理

在scrapy_redis中,所有的待抓取的对象和去重的指纹都存在公用的redis中

所有的服务器公用同一redis中的请求对象的队列

所有的request对象存入redis前,都会通过请求对象的指纹进行判断,之前是否已经存入过


scrapy_redis原理分析并实现断点续爬以及分布式爬虫

学习目标

了解 scrapy实现去重的原理

了解 scrapy中请求入队的条件

掌握 scrapy_redis基于url地址的增量式单机爬虫

掌握 scrapy_redis分布式爬虫


1. 下载github的demo代码

clone github scrapy-redis源码文件

git clone https://github.com/rolando/scrapy-redis.git

研究项目自带的demo

mv scrapy-redis/example-project ~/scrapyredis-project

2. 观察dmoz文件

在domz爬虫文件中,实现方式就是之前的crawlspider类型的爬虫

from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule ? ? class DmozSpider(CrawlSpider): ? """Follow categories and extract links.""" ? name = 'dmoz' ? allowed_domains = ['dmoztools.net'] ? start_urls = ['http://dmoztools.net/'] # 这里修改了url ? ? ? # 定义数据提取规则,使用了css选择器 ? rules = [ ? ? ? Rule(LinkExtractor( ? ? ? ? ? restrict_css=('.top-cat', '.sub-cat', '.cat-item') ? ? ? ), callback='parse_directory', follow=True), ? ] ? ? def parse_directory(self, response): ? ? ? for div in response.css('.title-and-desc'): ? ? ? ? ? yield { ? ? ? ? ? ? ? 'name': div.css('.site-title::text').extract_first(), ? ? ? ? ? ? ? 'description': div.css('.site-descr::text').extract_first().strip(), ? ? ? ? ? ? ? 'link': div.css('a::attr(href)').extract_first(), ? ? ? ? ? } ?

但是在settings.py中多了以下内容,这几行表示scrapy_redis中重新实现的了去重的类,以及调度器,并且使用RedisPipeline管道类

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_PERSIST = True ? ITEM_PIPELINES = { ? 'example.pipelines.ExamplePipeline': 300, ? 'scrapy_redis.pipelines.RedisPipeline': 400, } 3. 运行dmoz爬虫,观察现象

首先我们需要添加redis的地址,程序才能够使用redis

REDIS_URL = "redis://127.0.0.1:6379" #或者使用下面的方式 # REDIS_HOST = "127.0.0.1" # REDIS_PORT = 6379

我们执行domz的爬虫,会发现redis中多了一下三个键:

中止进程后再次运行dmoz爬虫

继续执行程序,会发现程序在前一次的基础之上继续往后执行,所以domz爬虫是一个基于url地址的增量式的爬虫

4. scrapy_redis的原理分析

我们从settings.py中的三个配置来进行分析 分别是:

RedisPipeline # 管道类

RFPDupeFilter # 指纹去重类

Scheduler # 调度器类

SCHEDULER_PERSIST # 是否持久化请求队列和指纹集合

4.1 Scrapy_redis之RedisPipeline

RedisPipeline中观察process_item,进行数据的保存,存入了redis中

4.2 Scrapy_redis之RFPDupeFilter

RFPDupeFilter 实现了对request对象的加密

4.3 Scrapy_redis之Scheduler

scrapy_redis调度器的实现了决定什么时候把request对象加入带抓取的队列,同时把请求过的request对象过滤掉

4.4 由此可以总结出request对象入队的条件

request的指纹不在集合中

request的dont_filter为True,即不过滤

start_urls中的url地址会入队,因为他们默认是不过滤

4.5 实现单机断点续爬

改写网易招聘爬虫,该爬虫就是一个经典的基于url地址的增量式爬虫

5. 实现分布式爬虫

5.1 分析demo中代码

打开example-project项目中的myspider_redis.py文件

通过观察代码:

继承自父类为RedisSpider

增加了一个redis_key的键,没有start_urls,因为分布式中,如果每台电脑都请求一次start_url就会重复

多了__init__方法,该方法不是必须的,可以手动指定allow_domains

启动方法:

在每个节点正确的目录下执行scrapy crawl 爬虫名,使该节点的scrapy_redis爬虫程序就位

在共用的redis中 lpush redis_key 'start_url',使全部节点真正的开始运行

settings.py中关键的配置

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_PERSIST = True ? ITEM_PIPELINES = { ? 'example.pipelines.ExamplePipeline': 300, ? 'scrapy_redis.pipelines.RedisPipeline': 400, } REDIS_URL = "redis://127.0.0.1:6379"

5.2 动手实现分布式爬虫

改写tencent爬虫为分布式爬虫

注意:启动方式发生改变


小结

scrapy_redis的含义和能够实现的功能

scrapy是框架

scrapy_redis是scrapy的组件

scrapy_redis能够实现断点续爬和分布式爬虫

scrapy_redis流程和实现原理

在scrapy框架流程的基础上,把存储request对象放到了redis的有序集合中,利用该有序集合实现了请求队列

并对request对象生成指纹对象,也存储到同一redis的集合中,利用request指纹避免发送重复的请求

request对象进入队列的条件

request的指纹不在集合中

request的dont_filter为True,即不过滤

request指纹的实现

请求方法

排序后的请求地址

排序并处理过的请求体或空字符串

用hashlib.sha1()对以上内容进行加密

scarpy_redis实现增量式爬虫、布式爬虫

对setting进行如下设置

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

SCHEDULER = "scrapy_redis.scheduler.Scheduler"

SCHEDULER_PERSIST = True

ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400,}

REDIS_URL = "redis://127.0.0.1:6379" # 请正确配置REDIS_URL

爬虫文件中的爬虫类继承RedisSpider类

爬虫类中redis_key替代了start_urls

启动方式不同

通过scrapy crawl spider启动爬虫后,向redis_key放入一个或多个起始url(lpush或rpush都可以),才能够让scrapy_redis爬虫运行

除了以上差异点以外,scrapy_redis爬虫和scrapy爬虫的使用方法都是一样的


scrapy_splash组件的使用

学习目标

了解 scrapy_splash组件的作用

了解 scrapy_splash组件的使用


1. 什么是scrapy_splash?

scrapy_splash是scrapy的一个组件

scrapy-splash加载js数据是基于Splash来实现的。

Splash是一个Javascript渲染服务。它是一个实现了HTTP API的轻量级浏览器,Splash是用Python和Lua语言实现的,基于Twisted和QT等模块构建。

使用scrapy-splash最终拿到的response相当于是在浏览器全部渲染完成以后的网页源代码。

splash官方文档 Splash - A javascript rendering service — Splash 3.5 documentation

2. scrapy_splash的作用

scrapy-splash能够模拟浏览器加载js,并返回js运行后的数据

3. scrapy_splash的环境安装

3.1 使用splash的docker镜像

splash的dockerfile https://github.com/scrapinghub/splash/blob/master/Dockerfile

观察发现splash依赖环境略微复杂,所以我们可以直接使用splash的docker镜像

如果不使用docker镜像请参考 splash官方文档 安装相应的依赖环境

3.1.1 安装并启动docker服务

3.1.2 获取splash的镜像

在正确安装docker的基础上pull取splash的镜像

sudo docker pull scrapinghub/splash

3.1.3 验证是否安装成功

运行splash的docker服务,并通过浏览器访问8050端口验证安装是否成功

前台运行 sudo docker run -p 8050:8050 scrapinghub/splash

后台运行 sudo docker run -d -p 8050:8050 scrapinghub/splash

访问 http://127.0.0.1:8050 看到如下截图内容则表示成功

3.1.4 解决获取镜像超时:修改docker的镜像源

以ubuntu18.04为例

创建并编辑docker的配置文件

sudo vi /etc/docker/daemon.json

写入国内docker-cn.com的镜像地址配置后保存退出

{ "registry-mirrors": ["https://registry.docker-cn.com"] }

重启电脑或docker服务后重新获取splash镜像

这时如果还慢,请使用手机热点(流量orz)

3.1.5 关闭splash服务

需要先关闭容器后,再删除容器

sudo docker ps -a sudo docker stop CONTAINER_ID sudo docker rm CONTAINER_ID

3.2 在python虚拟环境中安装scrapy-splash包

pip install scrapy-splash

4. 在scrapy中使用splash

以baidu为例

4.1 创建项目创建爬虫

scrapy startproject test_splash cd test_splash scrapy genspider no_splash baidu.com scrapy genspider with_splash baidu.com

4.2 完善settings.py配置文件

在settings.py文件中添加splash的配置以及修改robots协议

# 渲染服务的url SPLASH_URL = 'http://127.0.0.1:8050' # 下载器中间件 DOWNLOADER_MIDDLEWARES = { ? 'scrapy_splash.SplashCookiesMiddleware': 723, ? 'scrapy_splash.SplashMiddleware': 725, ? 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } # 去重过滤器 DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' # 使用Splash的Http缓存 HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' ? # Obey robots.txt rules ROBOTSTXT_OBEY = False

4.3 不使用splash

在spiders/no_splash.py中完善

import scrapy ? ? class NoSplashSpider(scrapy.Spider): ? name = 'no_splash' ? allowed_domains = ['baidu.com'] ? start_urls = ['https://·/s?wd=13161933309'] ? ? def parse(self, response): ? ? ? with open('no_splash.html', 'w') as f: ? ? ? ? ? f.write(response.body.decode())

4.4 使用splash

import scrapy from scrapy_splash import SplashRequest # 使用scrapy_splash包提供的request对象 ? class WithSplashSpider(scrapy.Spider): ? name = 'with_splash' ? allowed_domains = ['baidu.com'] ? start_urls = ['https://·/s?wd=13161933309'] ? ? def start_requests(self): ? ? ? yield SplashRequest(self.start_urls[0], ? ? ? ? ? ? ? ? ? ? ? ? ? callback=self.parse_splash, ? ? ? ? ? ? ? ? ? ? ? ? ? args={'wait': 10}, # 最大超时时间,单位:秒 ? ? ? ? ? ? ? ? ? ? ? ? ? endpoint='render.html') # 使用splash服务的固定参数 ? ? def parse_splash(self, response): ? ? ? with open('with_splash.html', 'w') as f: ? ? ? ? ? f.write(response.body.decode()) ?

4.5 分别运行俩个爬虫,并观察现象

4.5.1 分别运行俩个爬虫

scrapy crawl no_splash scrapy crawl with_splash

4.6 结论

splash类似selenium,能够像浏览器一样访问请求对象中的url地址

能够按照该url对应的响应内容依次发送请求

并将多次请求对应的多次响应内容进行渲染

最终返回渲染后的response响应对象


小结

scrapy_splash组件的作用

splash类似selenium,能够像浏览器一样访问请求对象中的url地址

能够按照该url对应的响应内容依次发送请求

并将多次请求对应的多次响应内容进行渲染

最终返回渲染后的response响应对象

scrapy_splash组件的使用

需要splash服务作为支撑

构造的request对象变为splash.SplashRequest

以下载中间件的形式使用

需要scrapy_splash特定配置

scrapy_splash的特定配置

SPLASH_URL = 'http://127.0.0.1:8050' DOWNLOADER_MIDDLEWARES = { ? 'scrapy_splash.SplashCookiesMiddleware': 723, ? 'scrapy_splash.SplashMiddleware': 725, ? 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
scrapy的日志信息与配置

学习目标:

了解 scrapy的日志信息

掌握 scrapy的常用配置

掌握 scrapy_redis配置

了解scrapy_splash配置

了解scrapy_redis和scrapy_splash配合使用的配置


1. 了解scrapy的日志信息 2. scrapy的常用配置

ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守

关于robots协议

在百度搜索中,不能搜索到淘宝网中某一个具体的商品的详情页面,这就是robots协议在起作用

Robots协议:网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,但它仅仅是互联网中的一般约定

例如:淘宝的robots协议

USER_AGENT 设置ua

DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用

ITEM_PIPELINES 管道,左位置右权重:权重值越小,越优先执行

SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同

DOWNLOADER_MIDDLEWARES 下载中间件

COOKIES_ENABLED 默认为True表示开启cookie传递功能,即每次请求带上前一次的cookie,做状态保持

COOKIES_DEBUG 默认为False表示日志中不显示cookie的传递过程

LOG_LEVEL 默认为DEBUG,控制日志的等级

LOG_LEVEL = "WARNING"

LOG_FILE 设置log日志文件的保存路径,如果设置该参数,日志信息将写入文件,终端将不再显示,且受到LOG_LEVEL日志等级的限制

LOG_FILE = "./test.log"

3. scrapy_redis配置

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指纹生成以及去重类

SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 调度器类

SCHEDULER_PERSIST = True # 持久化请求队列和指纹集合

ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400} # 数据存入redis的管道

REDIS_URL = "redis://host:port" # redis的url

4. scrapy_splash配置 SPLASH_URL = 'http://127.0.0.1:8050' DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' 5. scrapy_redis和scrapy_splash配合使用的配置

5.1 原理

scrapy-redis中配置了”DUPEFILTER_CLASS” : “scrapy_redis.dupefilter.RFPDupeFilter”,与scrapy-splash配置的DUPEFILTER_CLASS = ‘scrapy_splash.SplashAwareDupeFilter’ 相冲突!

查看了scrapy_splash.SplashAwareDupeFilter源码后,发现他继承了scrapy.dupefilter.RFPDupeFilter,并重写了request_fingerprint()方法。

比较scrapy.dupefilter.RFPDupeFilter和scrapy_redis.dupefilter.RFPDupeFilter中的request_fingerprint()方法后,发现是一样的,因此重写了一个SplashAwareDupeFilter,继承scrapy_redis.dupefilter.RFPDupeFilter,其他代码不变。

5.2 重写dupefilter去重类,并在settings.py中使用

5.2.1 重写去重类

from __future__ import absolute_import ? from copy import deepcopy ? from scrapy.utils.request import request_fingerprint from scrapy.utils.url import canonicalize_url ? from scrapy_splash.utils import dict_hash ? from scrapy_redis.dupefilter import RFPDupeFilter ? ? def splash_request_fingerprint(request, include_headers=None): ? """ Request fingerprint which takes 'splash' meta key into account """ ? ? fp = request_fingerprint(request, include_headers=include_headers) ? if 'splash' not in request.meta: ? ? ? return fp ? ? splash_options = deepcopy(request.meta['splash']) ? args = splash_options.setdefault('args', {}) ? ? if 'url' in args: ? ? ? args['url'] = canonicalize_url(args['url'], keep_fragments=True) ? ? return dict_hash(splash_options, fp) ? ? class SplashAwareDupeFilter(RFPDupeFilter): ? """ ? DupeFilter that takes 'splash' meta key in account. ? It should be used with SplashMiddleware. ? """ ? def request_fingerprint(self, request): ? ? ? return splash_request_fingerprint(request) ? ? """以上为重写的去重类,下边为爬虫代码""" ? from scrapy_redis.spiders import RedisSpider from scrapy_splash import SplashRequest ? ? class SplashAndRedisSpider(RedisSpider): ? name = 'splash_and_redis' ? allowed_domains = ['baidu.com'] ? ? # start_urls = ['https://·/s?wd=13161933309'] ? redis_key = 'splash_and_redis' ? # lpush splash_and_redis 'https://·' ? ? # 分布式的起始的url不能使用splash服务! ? # 需要重写dupefilter去重类! ? ? def parse(self, response): ? ? ? yield SplashRequest('https://·/s?wd=13161933309', ? ? ? ? ? ? ? ? ? ? ? ? ? callback=self.parse_splash, ? ? ? ? ? ? ? ? ? ? ? ? ? args={'wait': 10}, # 最大超时时间,单位:秒 ? ? ? ? ? ? ? ? ? ? ? ? ? endpoint='render.html') # 使用splash服务的固定参数 ? ? def parse_splash(self, response): ? ? ? with open('splash_and_redis.html', 'w') as f: ? ? ? ? ? f.write(response.body.decode())

5.2.2 scrapy_redis和scrapy_splash配合使用的配置

# 渲染服务的url SPLASH_URL = 'http://127.0.0.1:8050' # 下载器中间件 DOWNLOADER_MIDDLEWARES = { ? 'scrapy_splash.SplashCookiesMiddleware': 723, ? 'scrapy_splash.SplashMiddleware': 725, ? 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } # 使用Splash的Http缓存 HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' ? # 去重过滤器 # DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' # DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指纹生成以及去重类 DUPEFILTER_CLASS = 'test_splash.spiders.splash_and_redis.SplashAwareDupeFilter' # 混合去重类的位置 ? SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 调度器类 SCHEDULER_PERSIST = True # 持久化请求队列和指纹集合, scrapy_redis和scrapy_splash混用使用splash的DupeFilter! ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400} # 数据存入redis的管道 REDIS_URL = "redis://127.0.0.1:6379" # redis的url

注意:

scrapy_redis分布式爬虫在业务逻辑结束后并不能够自动退出

重写的dupefilter去重类可以自定义位置,也须在配置文件中写入相应的路径

6. 了解scrapy的其他配置

CONCURRENT_REQUESTS 设置并发请求的数量,默认是16个

DOWNLOAD_DELAY 下载延迟,默认无延迟,单位为秒


小结

了解scrapy的日志信息

掌握scrapy的常用配置

掌握scrapy_redis配置

了解scrapy_splash配置

了解scrapy_redis和scrapy_splash配合使用的配置


scrapyd部署scrapy项目

学习目标

了解 scrapyd的使用流程


1. scrapyd的介绍

scrapyd是一个用于部署和运行scrapy爬虫的程序,它允许你通过JSON API来部署爬虫项目和控制爬虫运行,scrapyd是一个守护进程,监听爬虫的运行和请求,然后启动进程来执行它们

所谓json api本质就是post请求的webapi

2. scrapyd的安装

scrapyd服务: pip install scrapyd

scrapyd客户端: pip install scrapyd-client

3. 启动scrapyd服务

在scrapy项目路径下 启动scrapyd的命令:sudo scrapyd 或 scrapyd

启动之后就可以打开本地运行的scrapyd,浏览器中访问本地6800端口可以查看scrapyd的监控界面

点击job可以查看任务监控界面

4. scrapy项目部署

4.1 配置需要部署的项目

编辑需要部署的项目的scrapy.cfg文件(需要将哪一个爬虫部署到scrapyd中,就配置该项目的该文件)

[deploy:部署名(部署名可以自行定义)] url = http://localhost:6800/ project = 项目名(创建爬虫项目时使用的名称)

4.2 部署项目到scrapyd

同样在scrapy项目路径下执行:

scrapyd-deploy 部署名(配置文件中设置的名称) -p 项目名称

部署成功之后就可以看到部署的项目

4.3 管理scrapy项目

启动项目:curl http://localhost:6800/schedule.json -d project=project_name -d spider=spider_name

关闭爬虫:curl http://localhost:6800/cancel.json -d project=project_name -d job=jobid

注意;curl是命令行工具,如果没有则需要额外安装

4.4 使用requests模块控制scrapy项目

import requests # 启动爬虫 url = 'http://localhost:6800/schedule.json' data = { 'project': 项目名, 'spider': 爬虫名, } resp = requests.post(url, data=data) # 停止爬虫 url = 'http://localhost:6800/cancel.json' data = { 'project': 项目名, 'job': 启动爬虫时返回的jobid, } resp = requests.post(url, data=data)
小结

在scrapy项目路径下执行sudo scrapyd或scrapyd,启动scrapyd服务;或以后台进程方式启动nohup scrapyd > scrapyd.log 2>&1 &

部署scrapy爬虫项目scrapyd-deploy -p myspider

启动爬虫项目中的一个爬虫curl http://localhost:6800/schedule.json -d project=myspider -d spider=tencent


13.Gerapy

学习目标

了解 什么是Gerapy

掌握 Gerapy的安装

掌握 Gerapy配置启动

掌握 通过Gerapy配置管理scrapy项目

1.Gerapy介绍:

Gerapy 是一款 分布式爬虫管理框架,支持 Python 3,基于 Scrapy、Scrapyd、Scrapyd-Client、Scrapy-Redis、Scrapyd-API、Scrapy-Splash、Jinjia2、Django、Vue.js 开发,Gerapy 可以帮助我们: ?

更方便地控制爬虫运行

更直观地查看爬虫状态

更实时地查看爬取结果

更简单地实现项目部署

更统一地实现主机管理

2.Gerapy的安装

1.执行如下命令,等待安装完毕

pip3 install gerapy

2.验证gerapy是否安装成功

在终端中执行 gerapy 会出现如下信息

""" ? Usage: ? gerapy init [--folder=<folder>] ? gerapy migrate ? gerapy createsuperuser ? gerapy runserver [host:port] ? """

3.Gerapy配置启动

1.新建一个项目

gerapy init

执行完该命令之后会在当前目录下生成一个gerapy文件夹,进入该文件夹,会找到一个名为projects的文件夹 ?

2.对数据库进行初始化(在gerapy目录中操作),执行如下命令

gerapy migrate

对数据库初始化之后会生成一个SQLite数据库,数据库保存主机配置信息和部署版本等 ?

3.启动 gerapy服务

gerapy runserver

此时启动gerapy服务的这台机器的8000端口上开启了Gerapy服务,在浏览器中输入http://localhost:8000就能进入Gerapy管理界面,在管理界面就可以进行主机管理和界面管理 ?

4.通过Gerapy配置管理scrapy项目

配置主机 1.添加scrapyd主机

需要添加 IP、端口,以及名称,点击创建即可完成添加,点击返回即可看到当前添加的 Scrapyd 服务列表,创建成功后,我们可以在列表中查看已经添加的服务

2.执行爬虫,就点击调度.然后运行. (前提是: 我们配置的scrapyd中,已经发布了爬虫.)

?

配置Projects 1.我们可以将scarpy项目直接放到 /gerapy/projects下.

2.可以在gerapy后台看到有个项目

3.点击部署点击部署按钮进行打包和部署,在右下角我们可以输入打包时的描述信息,类似于 Git 的 commit 信息,然后点击打包按钮,即可发现 Gerapy 会提示打包成功,同时在左侧显示打包的结果和打包名称。

4.选择一个站点,点击右侧部署,将该项目部署到该站点上

5.成功部署之后会显示描述和部署时间

6.来到clients界面,找到部署该项目的节点,点击调度

7.在该节点中的项目列表中找到项目,点击右侧run运行项目

补充: 1.Gerapy 与 scrapyd 有什么关联吗?

我们仅仅使用scrapyd是可以调用scrapy进行爬虫. 只是需要使用命令行开启爬虫 ? curl http://127.0.0.1:6800/schedule.json -d project=工程名 -d spider=爬虫名 ? 使用Greapy就是为了将使用命令行开启爬虫变成 “小手一点”. 我们在gerapy中配置了scrapyd后,不需要使用命令行,可以通过图形化界面直接开启爬虫.

小结

了解 什么是Gerapy

掌握 Gerapy的安装

掌握 Gerapy配置启动

掌握 通过Gerapy配置管理scrapy项目

## scrapy的crawlspider爬虫

学习目标:

了解 crawlspider的作用

应用 crawlspider爬虫创建的方法

应用 crawlspider中rules的使用


1 crawlspider是什么

回顾之前的代码中,我们有很大一部分时间在寻找下一页的url地址或者是内容的url地址上面,这个过程能更简单一些么?

思路:

从response中提取所有的满足规则的url地址

自动的构造自己requests请求,发送给引擎

对应的crawlspider就可以实现上述需求,能够匹配满足条件的url地址,组装成Reuqest对象后自动发送给引擎,同时能够指定callback函数

即:crawlspider爬虫可以按照规则自动获取连接

2 创建crawlspider爬虫并观察爬虫内的默认内容

2.1 创建crawlspider爬虫:

scrapy genspider -t crawl job 163.com

2.2 spider中默认生成的内容如下:

class JobSpider(CrawlSpider): name = 'job' allowed_domains = ['163.com'] start_urls = ['https://hr.163.com/position/list.do'] rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) def parse_item(self, response): i = {} #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() #i['name'] = response.xpath('//div[@id="name"]').extract() #i['description'] = response.xpath('//div[@id="description"]').extract() return i

2.3 观察跟普通的scrapy.spider的区别

在crawlspider爬虫中,没有parse函数

重点在rules中:

rules是一个元组或者是列表,包含的是Rule对象

Rule表示规则,其中包含LinkExtractor,callback和follow等参数

LinkExtractor:连接提取器,可以通过正则或者是xpath来进行url地址的匹配

callback :表示经过连接提取器提取出来的url地址响应的回调函数,可以没有,没有表示响应不会进行回调函数的处理

follow:连接提取器提取的url地址对应的响应是否还会继续被rules中的规则进行提取,True表示会,Flase表示不会

3. crawlspider网易招聘爬虫

通过crawlspider爬取网易招聘的详情页的招聘信息

url:职位搜索

思路分析:

定义一个规则,来进行列表页翻页,follow需要设置为True

定义一个规则,实现从列表页进入详情页,并且指定回调函数

在详情页提取数据

注意:连接提取器LinkExtractor中的allow对应的正则表达式匹配的是href属性的值

4 crawlspider使用的注意点:

除了用命令scrapy genspider -t crawl <爬虫名> <allowed_domail>创建一个crawlspider的模板,页可以手动创建

crawlspider中不能再有以parse为名的数据提取方法,该方法被crawlspider用来实现基础url提取等功能

Rule对象中LinkExtractor为固定参数,其他callback、follow为可选参数

不指定callback且follow为True的情况下,满足rules中规则的url还会被继续提取和请求

如果一个被提取的url满足多个Rule,那么会从rules中选择一个满足匹配条件的Rule执行

5 了解crawlspider其他知识点

链接提取器LinkExtractor的更多常见参数

allow: 满足括号中的're'表达式的url会被提取,如果为空,则全部匹配

deny: 满足括号中的're'表达式的url不会被提取,优先级高于allow

allow_domains: 会被提取的链接的domains(url范围),如:['hr.tencent.com', 'baidu.com']

deny_domains: 不会被提取的链接的domains(url范围)

restrict_xpaths: 使用xpath规则进行匹配,和allow共同过滤url,即xpath满足的范围内的url地址会被提取,如:restrict_xpaths='//div[@class="pagenav"]'

Rule常见参数

LinkExtractor: 链接提取器,可以通过正则或者是xpath来进行url地址的匹配

callback: 表示经过连接提取器提取出来的url地址响应的回调函数,可以没有,没有表示响应不会进行回调函数的处理

follow: 连接提取器提取的url地址对应的响应是否还会继续被rules中的规则进行提取,默认True表示会,Flase表示不会

process_links: 当链接提取器LinkExtractor获取到链接列表的时候调用该参数指定的方法,这个自定义方法可以用来过滤url,且这个方法执行后才会执行callback指定的方法

总结

crawlspider的作用:crawlspider可以按照规则自动获取连接

crawlspider爬虫的创建:scrapy genspider -t crawl tencent hr.tencent.com

crawlspider中rules的使用:

rules是一个元组或者是列表,包含的是Rule对象

Rule表示规则,其中包含LinkExtractor,callback和follow等参数

LinkExtractor:连接提取器,可以通过正则或者是xpath来进行url地址的匹配

callback :表示经过连接提取器提取出来的url地址响应的回调函数,可以没有,没有表示响应不会进行回调函数的处理

follow:连接提取器提取的url地址对应的响应是否还会继续被rules中的规则进行提取,True表示会,Flase表示不会

完成网易招聘爬虫crawlspider版本

八、appium的使用 利用appium自动控制移动设备并提取数据

学习目标

了解 appium-python-client模块定位元素以及提取其文本内容的方法

了解 appium-python-client模块控制滑动动作的方法


以控制抖音app滑动并获取抖音短视频发布者昵称和点赞数等信息为例

2.1 安装appium-python-client模块并启动已安装好的环境

2.1.1 安装appium-python-client模块

在window的虚拟环境下执行pip install appium-python-client

2.1.2 启动夜神模拟器,进入夜神模拟器所在的安装路径的bin目录下,进入cmd终端,使用adb命令建立adb server和模拟器的连接

adb devices

C:\Program Files (x86)\Nox\bin>adb devices List of devices attached * daemon not running; starting now at tcp:5037 * daemon started successfully

nox_adb.exe connect 127.0.0.1:62001

C:\Program Files (x86)\Nox\bin>nox_adb.exe connect 127.0.0.1:62001 already connected to 127.0.0.1:62001

adb devices

C:\Program Files (x86)\Nox\bin>adb devices List of devices attached 127.0.0.1:62001 device

2.1.3 启动appium-desktop,点击start server启动appium服务

[Appium] Welcome to Appium v1.10.0 [Appium] Appium REST http interface listener started on 0.0.0.0:4723

2.1.4 利用上一小节所学习的内容获取Desired Capabilities参数

获取模拟设备的型号

打开设置——关于平板电脑

查看型号,获取模拟设备的型号

获取app包名称 以及 app进程名

打开模拟器中的抖音短视频app

在adb连接正确的情况下,在夜神模拟器安装目录的bin目录下的cmd中输入adb shell

进入adb shell后输入 dumpsys activity | grep mFocusedActivity

`com.ss.android.ugc.aweme就是app包名

.main.MainActivity就是进程名 注意前边有个点.

2.2 初始化以及获取移动设备分辨率

完成代码如下,并运行代码查看效果:如果模拟器中抖音app被启动,并打印出模拟设备的分辨率则成功

from appium import webdriver ? # 初始化配置,设置Desired Capabilities参数 desired_caps = { ? 'platformName': 'Android', ? 'deviceName': 'SM-G955F', ? 'appPackage': 'com.ss.android.ugc.aweme', ? 'appActivity': '.main.MainActivity' } # 指定Appium Server server = 'http://localhost:4723/wd/hub' # 新建一个driver driver = webdriver.Remote(server, desired_caps) # 获取模拟器/手机的分辨率(px) width = driver.get_window_size()['width'] height = driver.get_window_size()['height'] print(width, height)

移动设备分辨率

driver.get_window_size()['width']

driver.get_window_size()['height']

2.3 定位元素以及提取文本的方法

2.3.1 点击appium desktop右上角的放大镜图标

如图填写配置,并点击start session

2.3.2 定位界面的使用方法如下图所示

2.3.3 点击短视频的作者名字,查看并获取该元素的id

2.3.4 在python使用代码通过元素id获取该元素的文本内容

实例化appium driver对象后添加如下代码,运行并查看效果

# 获取视频的各种信息:使用appium desktop定位元素 print(driver.find_element_by_id('bc').text) # 发布者名字 print(driver.find_element_by_id('al9').text) # 点赞数 print(driver.find_element_by_id('al_').text) # 留言数 print(driver.find_element_by_id('a23').text) # 视频名字,可能不存在,报错

定位元素及获取其文本内容的方法

driver.find_element_by_id(元素的id).text

driver.find_element_by_xpath(定位元素的xpath规则).text

2.4 控制抖音app滑动

2.4.1 appium滑动的函数

从(start_x, start_y)滑动到(end_x, end_y)

driver.swipe(start_x, start_y, end_x, end_y)

2.4.2 控制抖音app滑动的代码实现

start_x = width // 2 # 滑动的起始点的x坐标,屏幕宽度中心点 start_y = height // 3 * 2 # 滑动的起始点的y坐标,屏幕高度从上开始到下三分之二处 distance = height // 2 # y轴滑动距离:屏幕高度一半的距离 end_x = start_x # 滑动的终点的x坐标 end_y = start_y-distance # 滑动的终点的y坐标 # 滑动 driver.swipe(start_x, start_y, end_x, end_y)

2.5 整理并完成自动滑动的代码 import time from appium import webdriver ? ? class DouyinAction(): ? """自动滑动,并获取抖音短视频发布者的id""" ? def __init__(self, nums:int=None): ? ? ? # 初始化配置,设置Desired Capabilities参数 ? ? ? self.desired_caps = { ? ? ? ? ? 'platformName': 'Android', ? ? ? ? ? 'deviceName': 'SM-G955F', ? ? ? ? ? 'appPackage': 'com.ss.android.ugc.aweme', ? ? ? ? ? 'appActivity': '.main.MainActivity' ? ? ? } ? ? ? # 指定Appium Server ? ? ? self.server = 'http://localhost:4723/wd/hub' ? ? ? # 新建一个driver ? ? ? self.driver = webdriver.Remote(self.server, self.desired_caps) ? ? ? # 获取模拟器/手机的分辨率(px) ? ? ? width = self.driver.get_window_size()['width'] ? ? ? height = self.driver.get_window_size()['height'] ? ? ? print(width, height) ? ? ? # 设置滑动初始坐标和滑动距离 ? ? ? self.start_x = width//2 # 屏幕宽度中心点 ? ? ? self.start_y = height//3*2 # 屏幕高度从上开始到下三分之二处 ? ? ? self.distance = height//2 # 滑动距离:屏幕高度一半的距离 ? ? ? # 设置滑动次数 ? ? ? self.nums = nums ? ? def comments(self): ? ? ? # app开启之后点击一次屏幕,确保页面的展示 ? ? ? time.sleep(2) ? ? ? self.driver.tap([(500, 1200)], 500) ? ? def scroll(self): ? ? ? # 无限滑动 ? ? ? i = 0 ? ? ? while True: ? ? ? ? ? # 模拟滑动 ? ? ? ? ? print('滑动ing...') ? ? ? ? ? self.driver.swipe(self.start_x, self.start_y, ? ? ? ? ? ? ? ? ? ? ? ? ? ? self.start_x, self.start_y-self.distance) ? ? ? ? ? time.sleep(1) ? ? ? ? ? self.get_infos() # 获取视频发布者的名字 ? ? ? ? ? # 设置延时等待 ? ? ? ? ? time.sleep(4) ? ? ? ? ? # 判断是否退出 ? ? ? ? ? if self.nums is not None and self.nums == i: ? ? ? ? ? ? ? break ? ? ? ? ? i += 1 ? ? def get_infos(self): ? ? ? # 获取视频的各种信息:使用appium desktop定位元素 ? ? ? print(self.driver.find_element_by_id('bc').text) # 发布者名字 ? ? ? print(self.driver.find_element_by_id('al9').text) # 点赞数 ? ? ? print(self.driver.find_element_by_id('al_').text) # 留言数 ? ? ? print(self.driver.find_element_by_id('a23').text) # 视频名字,可能不存在,报错 ? ? ? ? # # 点击【分享】坐标位置 671,1058 ? ? ? # self.driver.tap([(671, 1058)]) ? ? ? # time.sleep(2) ? ? ? # # 向左滑动露出 【复制链接】 580,1100 --> 200, 1100 ? ? ? # self.driver.swipe(580,1100, 20, 200, 1100) ? ? ? # # self.driver.get_screenshot_as_file('./a.png') # 截图 ? ? ? # # 点击【复制链接】 距离右边60 距离底边170 720-60,1280-170 ? ? ? # self.driver.tap([(660, 1110)]) ? ? ? # # self.driver.get_screenshot_as_file('./b.png') # 截图 ? ? def main(self): ? ? ? self.comments() # 点击一次屏幕,确保页面的展示 ? ? ? time.sleep(2) ? ? ? self.scroll() # 滑动 ? ? if __name__ == '__main__': ? ? action = DouyinAction(nums=5) ? action.main()

小结

了解 appium-python-client模块定位元素以及提取其文本内容的方法

了解 appium-python-client模块控制滑动动作的方法

👇🏻 疑难解答可通过搜索下方?👇🏻


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Python #爬虫开发 #大家好我是辣条 #一爬虫基础爬虫概述知识点了解 #爬虫的概念了解 #爬虫的作用了解 #爬虫的分类掌握