Python爬虫开发从入门到实战

Python爬虫开发从入门到实战(微课版)


第1章 绪论

爬虫的主要目的是获取网页内容并解析。只要能达到这个目的,用什么方法都没有问题。

关于获取网页,本书主要介绍了Python的两个第三方模块,一个是requests,另一个是爬虫框架Scrapy。

关于解析网页内容,本书主要介绍了3种方式——正则表达式、XPath和BeautifulSoup。两种网页获取方式和3种网页解析方式可以自由搭配,随意使用。


第2章 Python基础

知识点

  • Python开发环境的搭建。
  • Python的基本知识、数据类型。
  • Python的条件语句和循环语句。
  • Python函数的定义和使用。
  • 基于Python的面向对象编程代码。

第3章 正则表达式与文件操作

知识点

  • 正则表达式的基本符号。
  • 如何在Python中使用正则表达式。
  • 正则表达式的提取技巧。
  • Python读写文本文件和CSV文件。

第4章 简单的网页爬虫开发

知识点

  • requests的安装和使用。
  • 多线程爬虫的开发。
  • 爬虫的常见算法。

多线程爬虫的开发

在掌握了requests与正则表达式以后,就可以开始实战爬取一些简单的网址了。

但是,此时的爬虫只有一个进程、一个线程,因此称为单线程爬虫。单线程爬虫每次只访问一个页面,不能充分利用计算机的网络带宽。一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速和从发起请求到得到源代码中间的时间都被浪费了。

如果可以让爬虫同时访问10个页面,就相当于爬取速度提高了10倍。为了达到这个目的,就需要使用多线程技术了。

微观上的单线程,在宏观上就像同时在做几件事。这种机制在I/O(Input/Output,输入/输出)密集型的操作上影响不大,但是在CPU计算密集型的操作上面,由于只能使用CPU的一个核,就会对性能产生非常大的影响。所以涉及计算密集型的程序,就需要使用多进程,Python的多进程不受GIL的影响。

由于爬虫是I/O密集型的操作,特别是在请求网页源代码的时候,如果使用单线程来开发,会浪费大量的时间来等待网页返回,所以把多线程技术应用到爬虫中,可以大大提高爬虫的运行效率。

多进程库(multiprocessing)

multiprocessing本身是Python的多进程库,用来处理与多进程相关的操作。但是由于进程与进程之间不能直接共享内存和堆栈资源,而且启动新的进程开销也比线程大得多,因此使用多线程来爬取比使用多进程有更多的优势。multiprocessing下面有一个dummy模块,它可以让Python的线程使用multiprocessing的各种方法。

dummy下面有一个Pool类,它用来实现线程池。这个线程池有一个map()方法,可以让线程池里面的所有线程都“同时”执行一个函数。

from multiprocessing.dummy import Pool as ThreadPool

# 使用map实现多线程爬虫
pool = ThreadPool(4)
pool.map(crawler_func, data_list)
pool.close()
pool.join()

常见搜索算法

  • DFS
  • BFS

在爬虫开发的过程中,应该选择深度优先还是广度优先呢?这就需要根据被爬取的数据来进行选择了。

小结

本章讲解了requests的安装和使用,以及如何使用Python的多进程库multiprocessing来实现多线程爬虫。


第5章 高性能HTML内容解析

知识点

  • HTML基础结构。
  • 使用XPath从HTML源代码中提取有用信息。
  • 使用Beautiful Soup4从HTML源代码中提取有用信息。

Beautiful Soup4

Beautiful Soup4(BS4)是Python的一个第三方库,用来从HTML和XML中提取数据。

pip install beautifulsoup4

小结

从网页中提取需要的信息,是爬虫开发中最重要但却最基本的操作。只有掌握并能自由运用正则表达式、XPath与Beautiful Soup4从网页中提取信息,爬虫的学习才算是入门。

XPath是一门查询语言,它由C语言开发而来,因此速度非常快。但是XPath需要经过一段时间的练习才能灵活应用。

Beautiful Soup4是一个从网页中提取数据的工具,它入门很容易,功能很强大,但是由于是基于Python开发的,因此速度比XPath要慢。读者可以自行选择喜欢的一项来作为自己主要的数据提取方式。本书选择使用XPath,所以后面的内容都会以XPath来进行讲解。


第6章 Python与数据库

数据库

本章将会讲解MongoDB和Redis这两个数据库。其中MongoDB用来保存大量数据,Redis用于作为缓存和队列保存临时数据。

知识点

  • MongoDB与Redis的安装。
  • MongoDB的增删改查操作。
  • Redis的列表与集合的操作。

在Mac OS下安装MongoDB

brew update
brew install mongodb
#启动MongoDB
mongod --config /usr/local/etc/mongod.conf

图形化管理工具—RoboMongo

RoboMongo是一个跨平台的MongoDB管理工具,可以在图形界面中查询或者修改MongoDB。

数据在MongoDB中是按照“库(Database)”—“集合(Collections)”—“文档(Document)”的层级关系来存储的。如果使用Python的数据结构来做类比的话,文档相当于一个字典,集合相当于一个包含了很多字典的列表,库相当于一个大字典,大字典里面的每一个键值对都对应了一个集合,Key为集合的名字,Value就是一个集合。

PyMongo的安装

PyMongo模块是Python对MongoDB操作的接口包,能够实现对MongoDB的增删改查及排序等操作。

pip install pymongo

PyMongo的使用

(1)使用PyMongo初始化数据库

要使用PyMongo操作MongoDB,首先需要初始化数据库连接。如果MongoDB运行在本地计算机上,而且也没有修改端口或者添加用户名及密码,那么初始化MongoClient的实例的时候就不需要带参数,直接写为:


from pymongo import MongoClient
client = MongoClient()

如果MongoDB是运行在其他服务器上面的,那么就需要使用“URI(Uniform Resource Identifier,统一资源标志符)”来指定连接地址。MongoDB URI的格式为:

mongodb://用户名:密码@服务器IP或域名:端口

PyMongo初始化数据库与集合有两种方式。

# 方式1:
from pymongo import MongoClient
client = MongoClient()
database= client.Chapter6
collection = database.spider
# 需要注意,使用方式1的时候,代码中的“Chapter6”和“spider”都不是变量名,它们直接就是库的名字和集合的名字。

# 方式2:
from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6'] 
collection = database['spider'] 

# 使用方式2时,在方括号中指定库名和集合名。这种情况下,方括号里除了直接写普通的字符串以外,还可以写一个变量。

默认情况下,MongoDB只允许本机访问数据库。这是因为MongoDB默认没有访问密码,出于安全性的考虑,不允许外网访问。

如果需要从外网访问数据库,那么需要修改安装MongoDB时用到的配置文件mongod.conf。

(2)插入数据

MongoDB的插入操作非常简单。用到的方法为insert(参数),插入的参数就是Python的字典。插入一条数据的代码如下。

from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6']
collection = database['spider'] 
data = {'id': 123, 'name': 'kingname', 'age': 20, 'salary': 999999}
collection.insert(data)

# MongoDB会自动添加一列“_id”,这一列里面的数据叫作ObjectId,ObjectId是在数据被插入MongoDB的瞬间,通过一定的算法计算出来的。因此,_id这一列就代表了数据插入的时间,它不重复,而且始终递增。通过一定的算法,可以把ObjectId反向恢复为时间。

将多个字典放入列表中,并将列表作为insert()方法的参数,即可实现批量插入数据,代码如下。

from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6']
collection = database['spider'] 
more_data = [
    {'id': 2, 'name': '张三', 'age': 10, 'salary': 0},
    {'id': 3, 'name': '李四', 'age': 30, 'salary': -100},
    {'id': 4, 'name': '王五', 'age': 40, 'salary': 1000},
    {'id': 5, 'name': '外国人', 'age': 50, 'salary': '未知'},
]
collection.insert(more_data)

(3)普通查找

MongoDB的查找功能对应的方法是:

find(查询条件, 返回字段)
find_one(查询条件,返回字段)


普通查询方法有以下3种写法。
content = collection.find() 
content = collection.find({'age': 29})
content = collection.find({'age': 29}, {'_id': 0, 'name': 1, 'salary': 1})

(4)逻辑查询

PyMongo也支持大于、小于、大于等于、小于等于、等于、不等于这类逻辑查询。

collection.find({'age': {'$gt': 29}}) #查询所有age > 29的记录
collection.find({'age': {'$gte': 29, '$lte': 40}})  #查询29 ≤ age ≤ 40的记录
collection.find({'salary': {'$ne: 29}}) #查询所有salary不等于29的记录

(5)对查询结果排序

# MongoDB支持对查询到的结果进行排序。排序的方法为sort()。它的格式为:
handler.find().sort('列名', 1-1)

# 查询一般和find()配合在一起使用。例如:
collection.find({'age': {'$gte': 29, '$lte': 40}}).sort('age', -1)
collection.find({'age': {'$gte': 29, '$lte': 40}}).sort('age', 1)

(6)更新记录

更新可使用update_one()和update_many()方法。它们的格式为:

collection.update_one(参数1, 参数2)
collection.update_many(参数1, 参数2)

(7)删除记录

删除可使用delete_one()和delete_many()方法。它们的格式为:

collection.delete_one(参数)
collection.delete_many(参数)

(8)对查询结果去重

去重使用distinct()方法,其格式为:


collection.distinct('列名')

设计一个开关

思考一个问题:如何设计一个开关,实现在不结束程序进程的情况下,从全世界任何一个有网络的地方既能随时暂停程序,又能随时恢复程序的运行。

最简单的方法就是用数据库来实现。在能被程序和控制者访问的服务器中创建一个数据库,数据库名为“Switch_DB”。数据库里面创建一个集合“Switch”,这个集合里面只有一个记录,就是“Status”,它只有两个值,“On”和“Off”,

在Mac OS下安装Redis

brew update
brew install redis
#运行Redis
redis-server /usr/local/etc/redis.conf

Redis交互环境的使用

redis-cli

常见操作

keys *可以查看当前有多少的“Key”。

在爬虫开发的过程中主要会用到Redis的列表与集合

(1)列表

Redis的列表是一个可读可写的双向队列

lpush key value1 value2 value 3…

如果想查看一个列表的长度,可使用关键字为“llen”。这个关键字的第1个“l”对应的是英文“list”(列表)的首字母。

如果不删除列表中的数据,又要把数据读出来,就需要使用关键字“lrange”,这里的“l”对应的是英文“list”的首字母。”lrange”的使用格式为:

lrange key start end
# 其中,start为起始位置,end为结束位置。例如:
lrange chapter_6 0 3

# 需要特别注意的是,在Python中,切片是左闭右开区间,例如,test[0:3]表示读列表的第0、1、2个共3个值。但是lrange的参数是一个闭区间,包括开始,也包括结束,因此在图6-35中会包含下标为0、1、2、3的4个值。

(2)集合

Redis的集合与Python的集合一样,没有顺序,值不重复。往集合中添加数据,使用的关键字为“sadd”。这里的“s”对应的是英文单词“set”(集合)。使用格式为:

sadd key value1 value2 value3

安装Redis-py

pip install redis

MongoDB的优化建议

少读少写少更新

  • 建议把要插入到MongoDB中的数据先统一放到一个列表中,等积累到一定量再一次性插入
  • 对于读数据,在内存允许的情况下,应该一次性把数据读入内存,尽量减少对MongoDB的读取操作。
  • 在某些情况下,更新操作不得不逐条进行。建议,把更新这个动作改为插入。这样就可以实现批量更新的效果了。具体来说,就是把数据批量插入到一个新的MongoDB集合中,再把原来的集合删除,最后将新的集合改为原来集合的名字。

能用Redis就不用MongoDB

为了提高效率,就需要引入Redis。由于Redis是基于内存的数据库,因此即使频繁对其读/写,对性能的影响也远远小于频繁读/写MongoDB。在Redis中创建一个集合“crawled_url”,爬虫在爬一个网址之前,先把这个网址sadd到这个集合中。如果返回为1,那么表示这个网址之前没有爬过,爬虫需要去爬取详情页。如果返回0,表示这个网址之前已经爬过了,就不需要再爬了。示例代码片段如下:

for url in url_list: #url_list为在贴吧列表页得到的每一个帖子的详情页网址列表
    if client.sadd('crawled_url', url) == 1:
        crawl(url)

练习

目标网站:http://dongyeguiwu.zuopinj.com/5525/。

目标内容:小说《白夜行》第一章到第十三章的正文内容。

任务要求:编写两个爬虫,爬虫1从http://dongyeguiwu.zuopinj.com/ 5525/获取小说《白夜行》第一章到第十三章的网址,并将网址添加到Redis里名为url_queue的列表中。爬虫2从Redis里名为url_queue的列表中读出网址,进入网址爬取每一章的具体内容,再将内容保存到MongoDB中。

# 1 使用XPath获取每一章的网址,再将它们添加到Redis中。其核心代码如下:
url_list = selector.xpath('//div[@class="book_list"]/ul/li/a/@href')
for url in url_list:
    client.lpush('url_queue', url)

# 2 对于爬取正文的爬虫,只要发现Redis里的url_queue这个列表不为空,就要从里面读出网址,并爬取数据。因此,其代码如下:
content_list = []
while client.llen('url_queue') > 0:
    url = client.lpop('url_queue').decode()
    source = requests.get(url).content
 
selector = html.fromstring(source)
chapter_name = selector.xpath('//div[@class="h1title"]/h1/text()')[0]
content = selector.xpath('//div[@id="htmlContent"]/p/text()')
content_list.append({'title': chapter_name, 'content': '\n'.join(content)})
handler.insert(content_list)


调试与运行

爬虫1运行结束以后,Redis中应该会出现一个名为url_queue的列表,执行以下代码:


llen url_queue

爬虫2运行结束以后,Redis中的url_queue会消失,同时MongoDB中会保存小说每一章的内容。

小结

本章主要讲解了MongoDB与Redis的使用。其中,MongoDB主要用来存放爬虫爬到的各种需要持久化保存的数据,而Redis则用来存放各种中间数据

通过减少频繁读/写MongoDB,并使用Redis来弥补MongoDB的一些不足,可以显著提高爬虫的运行效率。

动手实践

如果爬虫1把10000个网址添加到url_queue中,爬虫2同时运行在3台计算机上,请观察能实现什么效果。


第7章 异步加载与请求头

知识点

  • 抓取异步加载的数据。
  • 伪造HTTP请求头。
  • 模拟浏览器获取网站数据。

AJAX版登录页面的爬取

通过POST提交请求解决了AJAX版登录页面的爬取

小结

本章主要介绍了使用爬虫获取异步加载网页的各种方法。对于普通的异步加载,可以使用requests直接发送AJAX请求来获取被加载的内容。

发送的请求中可能包含一些特殊的值,这些值来自网页源代码或者另一个AJAX请求。

在发送请求时需要注意,应保持requests提交的请求头与浏览器的请求头一致,这样才能更好地骗过网站服务器达到获取数据的目的。

对于比较复杂的异步加载,现阶段可以先使用Selenium和ChromeDriver来直接加载网页,然后就能从被加载的网页中直接获取到需要的内容。


第8章 模拟登录与验证码

知识点

  • 使用Selenium操作浏览器实现自动登录网站。
  • 使用Cookies登录网站。
  • 模拟表单登录网站。
  • 爬虫识别简单的验证码。

模拟登录有多种实现方法

  • 使用Selenium操作浏览器登录
  • 使用Cookies登录虽然简单粗暴
  • 使用模拟提交表单登录虽然较为麻烦,但可以实现自动化

使用Cookies登录

Cookie是用户使用浏览器访问网站的时候网站存放在浏览器中的一小段数据。Cookie的复数形式Cookies用来表示各种各样的Cookie。它们有些用来记录用户的状态信息;有些用来记录用户的操作行为;还有一些,具有现代网络最重要的功能:记录授权信息——用户是否登录以及用户登录哪个账号。

为了不让用户每次访问网站都进行登录操作,浏览器会在用户第一次登录成功以后放一段加密的信息在Cookies中。下次用户访问,网站先检查Cookies有没有这个加密信息,如果有并且合法,那么就跳过登录操作,直接进入登录后的页面。

使用Cookie来登录网页,不仅可以绕过登录步骤,还可以绕过网站的验证码。

使用了requests的Session模块。

所谓Session,是指一段会话。网站会把每一个会话的ID(Session ID)保存在浏览器的Cookies中用来标识用户的身份。requests的Session模块可以自动保存网站返回的一些信息。其实在前面章节中使用的requests.get(),在底层还是会先创建一个Session,然后用Session去访问。

对于HTTPS的网站,在requests发送请求的时候需要带上verify=False这个参数,否则爬虫会报错。

带上这个参数以后,爬虫依然会报一个警告,这是因为没有HTTPS的证书。

对于HTTPS的网站,在requests发送请求的时候需要带上verify=False这个参数,否则爬虫会报错。

模拟表单登录

  • 通过POST提交请求解决了AJAX版登录页面的爬取。
  • 但是在现实中,有更多的网站是使用表单提交的方式来进行登录的。

使用requests的Session模块来模拟这个登录

验证码 - 肉眼打码

1.借助浏览器

在模拟登录中讲到过Cookies,通过Cookies能实现绕过登录,从而直接访问需要登录的网站。因此,对于需要输入验证码才能进行登录的网站,可以手动在浏览器登录网站,并通过Chrome获取Cookies,然后使用Cookies来访问网站。这样就可以实现人工输入一次验证码,然后很长时间不再登录。

2.不借助浏览器

对于仅仅需要识别图片的验证码,可以使用这种方式——先把验证码下载到本地,然后肉眼去识别并手动输入给爬虫。

验证码 - 自动打码

1.Python图像识别

Python的强大,在于它有非常多的第三方库。 对于验证码识别,Python也有现成的库来使用。开源的OCR库pytesseract配合图像识别引擎tesseract,可以用来将图片中的文字转换为文本。这种方式在爬虫中的应用并不多见。因为现在大部分的验证码都加上了干扰的纹理,已经很少能用单机版的图片识别方式来识别了。所以如果使用这种方式,只有两种情况:网站的验证码极其简单工整,使用大量的验证码来训练tesseract。

(1)安装tesseract


brew install tesseract

(2)安装Python库

要使用tesseract来进行图像识别,需要安装两个第三方库:


pip install Pillow

pip install pytesseract

# 其中,Pillow是Python中专门用来处理图像的第三方库,pytesseract是专门用来操作tesseract的第三方库。

(3)tesseract的使用

① 导入pytesseract和Pillow。

② 打开图片。

③ 识别。

import pytesseract
from PIL import Image
image = Image.open('验证码.png')
code = pytesseract.image_to_string(image)
print(code)

2.打码网站

(1)打码网站介绍

在线验证码识别的网站,简称打码网站。这些网站有一些是使用深度学习技术识别验证码,有一些是雇佣了很多人来人肉识别验证码

网站提供了接口来实现验证码识别服务。使用打码网站理论上可以识别任何使用输入方式来验证的验证码。

(2)使用在线打码

在百度或者谷歌上面搜索“验证码在线识别”,就可以找到很多提供在线打码的网站。但是由于一般这种打码网站是需要交费才能使用的,所以要注意财产安全。

云打码

练习:自动登录果壳网

目标网站:https://www.guokr.com。

目标内容:个人资料设置界面源代码。

使用模拟登录与验证码识别的技术实现自动登录果壳网。 果壳网的登录界面有验证码,请使用人工或者在线打码的方式识别验证码,并让爬虫登录。登录以后可以正确显示“个人资料设置”界面的源代码。

涉及的知识点:

  • 爬虫识别验证码。
  • 爬虫模拟登录。

小结

本章主要讲授了模拟登录与验证码识别。使用Selenium实现模拟登录最为简单。但是这种方式的弊端是运行速度慢。

使用Cookies登录可以实现一次手动、长期自动的目的。

而模拟表单登录本质就是发起POST请求来进行登录,需要使用Session模块来保存登录信息。

验证码识别主要是使需输入的验证码实现自动化。包括手动输入与在线打码。对于单击、拖动的验证码,建议使用Cookies来进行登录。


第9章 抓包与中间人爬虫

知识点

  • 使用Charles抓取App和微信小程序的数据包。
  • 使用mitmproxy开发中间人爬虫。

数据抓包

所谓抓包(Package Capture),简单来说,就是在网络数据传输的过程中对数据包进行截获、查看、修改或转发的过程。

如果把网络上发送与接收的数据包理解为快递包裹,那么在快递运输的过程中查看里面的内容,这就是抓包。

Charles

要简化寻找数据的过程,就需要设法直接全局搜索网页的所有请求的返回数据。

为了实现这个目的,就需要使用Charles。Charles是一个跨平台的HTTP抓包工具。使用它可以像Chrome一样截取HTTP或者HTTPS请求的数据包。

抓取HTTPS数据包

用Charles抓取HTTPS数据包时的请求会大量失败。出现这种情况,是因为没有安装SSL证书导致的。

第一步,安装好证书:要安装SSL证书,可选择菜单栏的“Help”- “SSL Proxying”-“Install Charles Root Certificate”命令。

第二步,设置SSL代理:安装好证书以后,选择菜单栏中的“Proxy”-“SSL Proxying Settings”命令打开SSL代理设置对话框。

iOS系统的配置和使用

对于苹果设备,首先要保证计算机和苹果设备联在同一个Wi-Fi上。选择Charles菜单栏中的“Help”-“Local IP Address”命令,此时弹出一个对话框,显示当前计算机的内网IP地址。

接下来设置手机。进入系统设置,选择“无线局域网”,然后单击已经连接的这个Wi-Fi热点右侧的圆圈包围的字母i的图标。

第一步,在手机上设置HTTP代理。

第二步,使用iOS系统自带的Safari浏览器访问https://chls.pro/ssl。安装证书。

第三步,证书信任设置

Android的配置和使用

将Charles的证书保存到计算机桌面

微信小程序爬虫

小程序的请求极其简单,基本上没有验证信息,即便有验证信息也非常脆弱。

用Python来请求小程序的后台接口从而获取数据,比请求异步加载网页的后台接口要容易很多。

在爬虫开发过程中,如果目标网站有微信小程序,那么一定要优先调查能否通过小程序的接口来抓取数据。

小程序的反爬虫能力比网页版的低很多。使用小程序的接口来爬数据,能极大提高爬虫的开发效率。

Charles的局限

  • Charles只能截获HTTP和HTTPS的数据包,如果网站使用的是websocket或者是flashsocket,那么Charles就无能为力。
  • 有一些App会自带证书,使用其他证书都无法正常访问后台接口。在这种情况下,Charles自带的证书就不能正常使用,也就没有办法抓取这种App的数据。
  • 有一些App的数据经过加密,App接收到数据以后在其内部进行解密。
  • 对于这种情况,Charles只能抓取到经过加密的数据。如果无法知道数据的具体加密方法,就没有办法解读Charles抓取到的数据。

中间人爬虫

中间人(Man-in-the-Middle,MITM)攻击是指攻击者与通信的两端分别创建独立的联系,并交换其所收到的数据,使通信的两端认为其正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

在中间人攻击中,攻击者可以拦截通信双方的通话,并插入新的内容或者修改原有内容。

例如:上课传纸条

中间人爬虫就是利用了中间人攻击的原理来实现数据抓取的一种爬虫技术。

数据抓包就是中间人爬虫的一个简单应用。所以使用Charles也是一种中间人攻击。

mitmproxy的介绍和安装

要安装mitmproxy,首先需要保证系统的Python为Python 3.5或者更高版本

brew install mitmproxy

mitmproxy使用

  • 运行mitmproxy会弹出对话框询问
mitmproxy
  • mitmproxy的端口为8080端口,在浏览器或者在手机上设置代理,代理IP为计算机IP,端口为8080端口。
  • 设置好代理以后,在手机上打开一个App或者打开一个网页,可以看到mitmproxy上面有数据滚动。
  • 此时只能访问HTTP网站,要访问HTTPS网站,还需要安装mitmproxy的证书。在手机设置了mitmproxy的代理以后,通过手机浏览器访问http://mitm.it/这个网址。

使用Python定制mitmproxy

mitmproxy的强大之处在于它还自带一个mitmdump命令。这个命令可以用来运行符合一定规则的Python脚本,并在Python脚本里面直接操作HTTP和HTTPS的请求,以及返回的数据包。

为了自动化地监控网站或者手机发出的请求头部信息和Body信息,并接收网站返回的头部信息和Body信息,就需要掌握如何在Python脚本中获得请求和返回的数据包。

mitmdump的使用场景

  • 网站返回的Headers中经常有Cookies。
  • mitmdump的脚本使用print()函数把Cookies打印出来,然后通过管道传递给另一个普通的正常的Python脚本。
  • 在另一个脚本里面,得到管道传递进来的Cookies,再把它放进Redis里面。

需求分析

目标App:Keep。

目标内容:Keep是当下热门的健身App,本次案例的目的是要使用抓包的方式爬取Keep的热门动态。

涉及的知识点:

  • 使用Charles或者mitmproxy抓包。
  • 开发App爬虫。

小结

  • 抓包是爬虫开发过程中非常有用的一个技巧。使用Charles,可以把爬虫的爬取范围从网页瞬间扩展到手机App和微信小程序。
  • 由于微信小程序的反爬虫机制在大多数情况下都非常脆弱,所以如果目标网站有微信小程序,那么可以大大简化爬虫的开发难度。
  • 当然,网站有可能会对接口的数据进行加密,App得到密文以后,使用内置的算法进行解密。对于这种情况,单纯使用抓包就没有办法处理了,就需要使用下一章所要讲到的技术来解决。
  • 使用mitmproxy可以实现爬虫的全自动化操作。
  • 对于拥有复杂参数的网站,使用这种先抓包再提交的方式可以在一定程度上绕过网站的反爬虫机制,从而实现数据抓取。

第10章 Android原生App爬虫

知识点

那么有没有什么办法可以做到几乎毫无痕迹地爬取数据呢?答案是有。当然可能有读者会认为可以使用Selenium + ChromeDriver。这种方式只能操作网页。本章将要介绍针对Android原生App的爬虫。

  • Android测试环境的搭建。
  • 使用Python操作Android手机。
  • 使用Python操作Android手机实现爬虫。

实现原理

目前,Android App主要有两种实现形式。第一种是Android原生App。这种App的全部或者大部分内容使用Android提供的各个接口来开发,例如Android版的微信就是一个Android原生的App。第二种是基于网页的App。这种App本质上就是一个浏览器,里面的所有内容实际上都是网页。例如,12306的App就是这样一种基于网页的App。

Android原生App爬虫(以下简称App爬虫)可以直接读取Android原生App上面的文本信息。

UI Automator Viewer

设置好环境变量以后,在终端窗口输入“uiautomatorviewer”并按Enter键,如果弹出UI Automator Viewer窗口,表明环境设置成功。

Android手机连接到计算机上,保持手机屏幕为亮起状态,单击UI Automator Viewer左上角文件夹右侧的手机图标,如果能够看到手机屏幕出现在窗口中,则表示一切顺利,环境搭建成功完成。如果在这个过程中手机弹出了任何警告窗口,都选择“运行”或者“确定”。

使用Python操纵手机

pip install uiautomator

有一点需要特别说明,UI Automator Viewer与Python uiautomator不能同时使用。

与Selenium一样,要操作手机上面的元素,首先要找到被操作的东西。以打开微信为例,首先翻到有微信的那一页

from uiautomator import Device
device = Device()
device(text='微信').click()

如果计算机上面只连接了一台Android手机,那么初始化设备连接只需要使用device = Device()即可。那么如果计算机上连接了很多台手机,该怎么办呢?此时就需要指定手机的串号。要查看手机串号,需要在终端输入以下命令:

adb devices -l

从输出的内容可以看到手机的串号

选择器

如何知道有哪些选择器可供使用呢?请执行以下代码:

from uiautomator import Device
device = Device()
print(device.dump())

此时终端会以XML输出当前手机屏幕显示的窗口布局信息。

这里的XML就相当于网页中的HTML,用来描述窗口上面各个部分的布局信息。

XML的格式与HTML非常像,格式为:<标签 属性1=“属性值1” 属性2=“属性值2”>文本

操作

  • 获得屏幕文字;
  • 滚动屏幕;
  • 滑动屏幕;
  • 点击屏幕;
  • 输入文字;
  • 判断元素是否存在;
  • 点亮关闭屏幕;
  • 操作实体按键;
  • watcher。

多设备应用(群控)

使用uiautomator来做爬虫开发,最主要的瓶颈在于速度。因为屏幕上的元素加载是需要时间的,这个时间受到手机性能和网速的多重限制。因此比较好的办法是使用多台Android手机实现分布式抓取。使用USBHub扩展计算机的USB口以后,一台计算机控制30台Android手机是完全没有问题的。只要能实现良好的调度和任务派分逻辑,就可以大大提高数据的抓取效率。

App爬虫系统架构的形式

练习:BOSS直聘爬虫

任务目标:BOSS直聘App。
BOSS直聘是一个招聘App,在上面可以看到很多的工作。 App职位列表如图10-44所示。
使用uiautomator开发一个爬虫,从手机上爬取每一个职位的名称、薪资、招聘公司、公司地址、工作经验要求和学历。

小结

  • 本章主要讲解了如何通过Python操作手机来获取Android原生App中的文字内容。Python使用uiautomator这个第三方库来操作Android手机的UiAutomator,从而实现模拟人们对手机屏幕的任何操作行为,并直接读取屏幕上的文字。
  • 使用uiautomator来开发爬虫,要打通流程非常简单。但是需要特别注意处理各种异常的情况。同时,由于手机速度的问题,应该使用多台手机构成一个集群来提高抓取的速率。
  • 最后,如果使用手机集群来进行数据抓取,并且需要抓取的App数据来自网络,那么需要考虑无线路由器的负荷。当同时连接无线路由器的设备超过一定数量时,有可能导致部分甚至所有设备都无法上网。这就需要使用工业级路由器来缓解。
  • 无线信号相互干扰也是一个比较麻烦的问题。使用5G信道能缓解,但一般便宜的Android手机不支持5G的Wi-Fi信道,此时能做的就是把手机尽量分开,不要放在一起。使用电磁屏蔽网,将每10个手机和一个无线路由器为一组包裹起来,也能起到一定的阻隔Wi-Fi信号的作用。

练习

使用Android手机来爬取一个原生App的数据。


第11章 Scrapy

知识点

  • 在Windows、Mac OS和Linux下搭建Scrapy环境。
  • 使用Scrapy获取网络源代码。
  • 在Scrapy中通过XPath解析数据。
  • 在Scrapy中使用MongoDB。
  • 在Scrapy中使用Redis。

在Mac OS下安装Scrapy

pip install scrapy

创建项目

$ scrapy startproject offcn
# offcn 是项目名

$ cd offcn
$ scrapy genspider jobbank zw.offcn.com
# jobbank 是爬虫名
# zw.offcn.com 是爬取的网址

# 运行
$ scrapy crawl jobbank

那么如何使用PyCharm来运行或者调试Scrapy的爬虫呢?

为了实现这个目的,需要创建另外一个Python文件。文件名可以取任意合法的文件名。这里以“main.py”为例。

main.py文件内容如下:

from scrapy import cmdline
cmdline.execute("scrapy crawl jobbank".split())

Scrapy的工程结构

  • spiders文件夹:存放爬虫文件的文件夹。
  • items.py:定义需要抓取的数据。
  • pipelines.py:负责数据抓取以后的处理工作。
  • settings.py:爬虫的各种配置信息。

但是为什么还有items.py和pipelines.py这两个文件呢?这是由于Scrapy的理念是将数据爬取和数据处理分开。

items.py文件用于定义需要爬取哪些内容。

pipelines.py文件用于对数据做初步的处理,包括但不限于初步清洗数据、存储数据等。在pipelines中可以将数据保存到MongoDB。

Scrapy与MongoDB

一个Scrapy工程可以有多个爬虫;再看items.py文件,可以发现,在一个items.py里面可以对不同的爬虫定义不同的抓取内容Item。

接下来设置pipelines.py。在这个文件中,需要写出将数据保存到MongoDB的代码。而这里的代码,就是最简单的初始化MongoDB的连接,保存数据。

Scrapy与Redis

Scrapy是一个分布式爬虫的框架,如果把它像普通的爬虫一样单机运行,它的优势将不会被体现出来。

因此,要让Scrapy往分布式爬虫方向发展,就需要学习Scrapy与Redis的结合使用。Redis在Scrapy的爬虫中作为一个队列存在。

使用Redis缓存网页并自动去重

pip install scrapy_redis

小结

本章主要讲了Python分布式爬虫框架Scrapy的安装和使用。

Scrapy在Windows中的安装最为烦琐,在Mac OS中的安装最为简单。

由于Scrapy需要依赖非常多的第三方库文件,因此建议无论使用哪个系统安装,都要将Scrapy安装到Virtualenv创建的虚拟Python环境中,从而避免影响系统的Python环境。

使用Scrapy爬取网页,最核心的部分是构建XPath。而其他的各种配置都是一次配好、终生使用的。由于Scrapy的理念是将数据抓取的动作和数据处理的动作分开,因此对于数据处理的各种逻辑应该让pipeline来处理。数据抓取的部分只需要关注如何使用XPath提取数据。数据提取完成以后,提交给pipeline处理即可。

由于Scrapy爬虫爬取数据是异步操作,所以从一个页面跳到另一个页面是异步的过程,需要使用回调函数。

Scrapy爬取到的数据量非常大,所以应该使用数据库来保存。使用MongoDB会让数据保存工作变得非常简单。

要让Scrapy使用MongoDB,只需要在pipeline中配置好数据保存的流程,再在settings.py中配置好ITEM_PIPELINES和MongoDB的信息即可。

使用Redis做缓存是从爬虫迈向分布式爬虫的一个起点。

Scrapy安装scrapy_redis组件以后,就可以具备使用Redis的能力。

在Scrapy中使用Redis需要修改爬虫的父类,需要在settings.py中设置好爬虫的调度和去重。

同时对于Python 3,需要修改scrapy_redis的一行代码,才能让爬虫正常运行。

练习

请完成网站爬虫开发,并实现爬虫的翻页功能,从而可以爬到1~5页所有的文章。下一页的URL请使用爬虫从当前页面获取,切勿根据URL的规律手动构造。

提示,对于翻页功能,实际上相当于将回调函数的函数名写成它自己的: callback=self.parse。parse方法可自己调用自己,不过传入的URL是下一页的URL。这有点像递归,不过递归用的是return,而这里用的是yield。

请思考一个问题,在请求文章详情页的时候,设置了请求头,但是Scrapy请求文章列表页的时候,在哪里设置请求头?请求列表页的时候,爬虫直接从Redis得到网址,自动发起了请求,完全没让开发者自己设置请求头。这其实是一个非常大的隐患,因为不设置请求头,网站立刻就能知道这个请求来自Scrapy,这是非常危险的。

请读者查询scrapy_redis的文档,查看如何使用make_requests_ from_url(self, url)这个方法。


第12章 Scrapy高级应用

知识点

  • 开发Scrapy中间件。
  • 使用Scrapyd部署爬虫。
  • Nginx的安装和反向代理。
  • 了解爬虫的分布式框架。

Middleware

中间件是Scrapy里面的一个核心概念。使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫。

“中间件”这个中文名字和前面章节讲到的“中间人”只有一字之差。它们做的事情确实也非常相似。中间件和中间人都能在中途劫持数据,做一些修改再把数据传递出去。不同点在于,中间件是开发者主动加进去的组件,而中间人是被动的,一般是恶意地加进去的环节。

中间件主要用来辅助开发,而中间人却多被用来进行数据的窃取、伪造甚至攻击。

在Scrapy中有两种中间件:下载器中间件(Downloader Middleware)和爬虫中间件(Spider Middleware)。

Downloader Middleware

更换代理IP,更换Cookies,更换User-Agent,自动重试。

加入中间件以后的爬虫流程

开发代理中间件

代理中间件的可用代理列表不一定非要写在settings.py里面,也可以将它们写到数据库或者Redis中。一个可行的自动更换代理的爬虫系统,应该有如下的3个功能。

  • 有一个小爬虫ProxySpider去各大代理网站爬取免费代理并验证,将可以使用的代理IP保存到数据库中。
  • 在ProxyMiddlerware的process_request中,每次从数据库里面随机选择一条代理IP地址使用。
  • 周期性验证数据库中的无效代理,及时将其删除。

激活中间件

中间件写好以后,需要去settings.py中启动。

Scrapy自带中间件及其顺序编号

开发UA中间件

从settings.py配置好的UA列表中随机选择一项,加入到请求头中

开发Cookies中间件

对于需要登录的网站,可以使用Cookies来保持登录状态。那么如果单独写一个小程序,用Selenium持续不断地用不同的账号登录网站,就可以得到很多不同的Cookies。由于Cookies本质上就是一段文本,所以可以把这段文本放在Redis里面。这样一来,当Scrapy爬虫请求网页时,可以从Redis中读取Cookies并给爬虫换上。这样爬虫就可以一直保持登录状态。

在中间件中集成Selenium

对于一些很麻烦的异步加载页面,手动寻找它的后台API代价可能太大。这种情况下可以使用Selenium和ChromeDriver或者Selenium和PhantomJS来实现渲染网页。

在中间件中集成了Selenium以后,就可以像爬取普通网页一样爬取异步加载的页面

在中间件里重试

在爬虫的运行过程中,可能会因为网络问题或者是网站反爬虫机制生效等原因,导致一些请求失败。在某些情况下,少量的数据丢失是无关紧要的,例如在几亿次请求里面失败了十几次,损失微乎其微,没有必要重试。但还有一些情况,每一条请求都至关重要,容不得有一次失败。此时就需要使用中间件来进行重试。

在中间件里处理异常

在默认情况下,一次请求失败了,Scrapy会立刻原地重试,再失败再重试,如此3次。如果3次都失败了,就放弃这个请求。这种重试逻辑存在一些缺陷。以代理IP为例,代理存在不稳定性,特别是免费的代理,差不多10个里面只有3个能用。而现在市面上有一些收费代理IP提供商,购买他们的服务以后,会直接提供一个固定的网址。

把这个网址设为Scrapy的代理,就能实现每分钟自动以不同的IP访问网站。如果其中一个IP出现了故障,那么需要等一分钟以后才会更换新的IP。在这种场景下,Scrapy自带的重试逻辑就会导致3次重试都失败。

这种场景下,如果能立刻更换代理就立刻更换;如果不能立刻更换代理,比较好的处理方法是延迟重试。而使用Scrapy_redis就能实现这一点。

爬虫的请求来自于Redis,请求失败以后的URL又放回Redis的末尾。

一旦一个请求原地重试3次还是失败,那么就把它放到Redis的末尾,这样Scrapy需要把Redis列表前面的请求都消费以后才会重试之前的失败请求。这就为更换IP带来了足够的时间。

下载器中间件功能总结

能在中间件中实现的功能,都能通过直接把代码写到爬虫中实现。使用中间件的好处在于,它可以把数据爬取和其他操作分开。在爬虫的代码里面专心写数据爬取的代码;在中间件里面专心写突破反爬虫、登录、重试和渲染AJAX等操作。

对团队来说,这种写法能实现多人同时开发,提高开发效率;对个人来说,写爬虫的时候不用考虑反爬虫、登录、验证码和异步加载等操作。另外,写中间件的时候不用考虑数据怎样提取。一段时间只做一件事,思路更清晰。

爬虫中间件

爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同。

下载器中间件的作用对象是请求request和返回response;爬虫中间键的作用对象是爬虫,更具体地来说,就是写在spiders文件夹下面的各个文件。

Scrapy的数据流图

其中,4、5表示下载器中间件,6、7表示爬虫中间件。爬虫中间件会在以下几种情况被调用。

  • 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的process_spider_output()方法被调用。
  • 当爬虫本身的代码出现了Exception的时候,爬虫中间件的process_spider_exception()方法被调用。
  • 当爬虫里面的某一个回调函数parse_xxx()被调用之前,爬虫中间件的process_spider_input()方法被调用。
  • 当运行到start_requests()的时候,爬虫中间件的process_start_ requests()方法被调用。

在中间件处理爬虫本身的异常

在爬虫中间件里面可以处理爬虫本身的异常。

下载器中间件里面的报错一般是由于外部原因引起的,和代码层面无关。而现在的这种报错是由于代码本身的问题导致的,是代码写得不够周全引起的。

激活爬虫中间件

在settings.py中,在下载器中间件配置项的上面就是爬虫中间件的配置项,它默认也是被注释了的,解除注释,并把自定义的爬虫中间件添加进去即可

几个自带的爬虫中间件

爬虫中间件输入/输出

在爬虫中间件里面还有两个不太常用的方法,分别为process_ spider_input(response, spider)和process_spider_output(response, result, spider)。其中,process_spider_input(response, spider)在下载器中间件处理完成后,马上要进入某个回调函数parse_xxx()前调用。

process_spider_output(response, result, output)是在爬虫运行yield item或者yield scrapy.Request()的时候调用。

在这个方法处理完成以后,数据如果是item,就会被交给pipeline;如果是请求,就会被交给调度器,然后下载器中间件才会开始运行。

所以在这个方法里面可以进一步对item或者请求做一些修改。这个方法的参数result就是爬虫爬出来的item或者scrapy.Request()。由于yield得到的是一个生成器,生成器是可以迭代的,所以result也是可以迭代的,可以使用for循环来把它展开。

爬虫的部署

一般情况下,爬虫会使用云服务器来运行,这样可以保证爬虫24h不间断运行。

FTP:使用FTP来上传代码,不仅非常不方便,而且经常出现把方向搞反,导致本地最新的代码被服务器代码覆盖的问题。

Git:好处是可以进行版本管理,不会出现代码丢失的问题。但操作步骤多,需要先在本地提交,然后登录服务器,再从服务器上面把代码下载下来。如果有很多服务器的话,每个服务器都登录并下载一遍代码是非常浪费时间的事情。

Docker:好处是可以做到所有服务器都有相同的环境,部署非常方便。但需要对Linux有比较深入的了解,对新人不友好,上手难度比较大。

为了克服上面的种种问题,本书将会使用Scrapy官方开发的爬虫部署、运行、管理工具:Scrapyd。

Scrapyd

Scrapyd是Scrapy官方开发的,用来部署、运行和管理Scrapy爬虫的工具。使用Scrapyd,可以实现一键部署Scrapy爬虫,访问一个网址就启动/停止爬虫。Scrapyd自带一个简陋网页,可以通过浏览器看到爬虫当前运行状态或者查阅爬虫Log。Scrapyd提供了官方API,从而可以通过二次开发实现更多更加复杂的功能。

Scrapyd可以同时管理多个Scrapy工程里面的多个爬虫的多个版本。如果在多个云服务器上安装Scrapyd,可以通过Python写一个小程序,来实现批量部署爬虫、批量启动爬虫和批量停止爬虫。

pip install scrapyd

上传Scrapy爬虫的工具

Scrapyd需要安装到云服务器上,如果读者没有云服务器,或者想在本地测试,那么可以在本地也安装一个。

接下来需要安装scrapyd-client,这是用来上传Scrapy爬虫的工具,也是Python的一个第三方库,使用pip安装即可:

pip install scrapyd-client

这个工具只需要安装到本地计算机上,不需要安装到云服务器上

启动Scrapyd

接下来需要在云服务器上启动Scrapyd。在默认情况下,Scrapyd不能从外网访问,为了让它能够被外网访问,需要创建一个配置文件。除了bind_address这一项外,其他都可以保持默认。bind_address这一项的值设定为当前这台云服务器的外网IP地址。配置文件放好以后,在终端或者CMD中输入scrapyd并按Enter键,这样Scrapyd就启动了。

scrapyd

此时打开浏览器,输入“http://云服务器IP地址:6800”格式的地址就可以打开Scrapyd自带的简陋网页

部署爬虫

Scrapyd启动以后,就可以开始部署爬虫了。打开任意一个Scrapy的工程文件,可以看到在工程的根目录中,Scrapy已经自动创建了一个scrapy.cfg文件,打开这个文件。现在需要把第10行的注释符号去掉,并将IP地址改为Scrapyd所在云服务器的IP地址

最后,使用终端或者CMD进入这个Scrapy工程的根目录,执行下面这一行命令部署爬虫:

scrapyd-deploy

Mac OS和Linux系统,启动/停止爬虫

curl这个发起网络请求的自带工具

在上传了爬虫以后,就可以启动爬虫了。对于Mac OS和Linux系统,启动和停止爬虫非常简单。要启动爬虫,需要在终端输入下面这一行格式的代码。

curl http://云服务器IP地址:6800/schedule.json -d project=爬虫工程名 -d spider=爬虫名

如果爬虫的运行时间太长,希望提前结束爬虫,那么可以使用下面格式的命令来实现:

curl http://爬虫服务器IP地址:6800/cancel.json -d project=工程名 -d job=爬虫JOBID(在网页上可以查询到)

运行以后,相当于对爬虫按下了Ctrl+C组合键的效果

Windows系统,启动/停止爬虫

没有像Mac OS和Linux的终端一样拥有curl这个发起网络请求的自带工具。但既然是发起网络请求,那么只需要借助Python和requests就可以完成。

  • 使用requests发送请求启动爬虫
  • 使用requests发送请求关闭爬虫

在Python中执行命令部署爬虫

由于部署爬虫的时候直接执行scrapyd-deploy命令,所以如何使用Python来自动化部署爬虫呢?其实也非常容易。在Python里面使用os这个自带模块就可以执行系统命令

批量部署

使用Python与requests的好处不仅在于可以帮助Windows实现控制爬虫,还可以用来实现批量部署、批量控制爬虫。

假设有一百台云服务器,只要每一台上面都安装了Scrapyd,那么使用Python来批量部署并启动爬虫所需要的时间不超过1min。

这里给出一个批量部署并启动爬虫的例子,首先在爬虫的根目录下创建一个scrapy_template.cfg文件

权限管理

为了弥补Scrapyd没有权限管理系统这一短板,就需要借助其他方式来对网络请求进行管控。带权限管理的反向代理就是一种解决办法。

Nginx的介绍

Nginx读作Engine X,是一个轻量级的高性能网络服务器、负载均衡器和反向代理。

为了解决Scrapyd的问题,需要用Nginx做反向代理。

正向代理与反向代理

所谓“反向代理”,是相对于“正向代理”而言的。前面章节所用到的代理是正向代理。正向代理帮助请求者(爬虫)隐藏身份,爬虫通过代理访问服务器,服务器只能看到代理IP,看不到爬虫;

反向代理帮助服务器隐藏身份。用户只知道服务器返回的内容,但不知道也不需要知道这个内容是在哪里生成的。

使用Nginx来保护Scrapyd

使用Nginx反向代理到Scrapyd以后,Scrapyd本身只需要开通内网访问即可。

用户访问Nginx,Nginx再访问Scrapyd,然后Scrapyd把结果返回给Nginx,Nginx再把结果返回给用户。这样只要在Nginx上设置登录密码,就可以间接实现Scrapyd的访问管控了

Nginx的安装

sudo apt-get install nginx

安装好以后,需要考虑以下两个问题。

  • 服务器是否有其他的程序占用了80端口。
  • 服务器是否有防火墙。

/etc/nginx/sites-available/default文件,将80全部改成81并保存

sudo systemctl restart nginx

配置反向代理

首先打开/etc/scrapyd/scrapyd.conf,把bind_address这一项重新改为127.0.0.1,并把http_port这一项改为6801。把Scrapyd设置为只允许内网访问,端口为6801

接下来配置Nginx,在/etc/nginx/sites-available文件夹下创建一个scrapyd.conf,其内容为:

server {
  listen 6800;
  location / {
      proxy_pass http://127.0.0.1:6801/;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
    }
  }

使用basic auth权限管理方法,对于通过授权的用户,将它对6800端口的请求转到服务器本地的6801端口。需要注意配置里面的记录授权文件的路径这一项:auth_basic_user_file /etc/nginx/conf.d/.htpasswd

在后面会将授权信息的记录文件放在/etc/nginx/conf.d/.htpasswd这个文件中。写好这个配置以后,保存。

接下来,执行下面的命令,在/etc/nginx/sites-enabled文件夹下创建一个软连接:

sudo ln -s /etc/nginx/sites-available/scrapyd.conf /etc/nginx/sites-enabled/

软连接创建好以后,需要生成账号和密码的配置文件。首先安装apache2-utils软件包:

sudo apt-get install apache2-utils

安装完成apache2-utils以后,cd进入/etc/nginx/conf.d文件夹,并执行命令为用户kingname生成密码文件:

sudo htpasswd -c .htpasswd kingname

上面的命令会在/etc/nginx/conf.d文件夹下生成一个.htpasswd的隐藏文件。有了这个文件,Nginx就能进行权限验证了。

接下来重启Nginx:

sudo systemctl restart nginx

重启完成以后,启动Scrapyd,再在浏览器上访问格式为“http://服务器IP:6800”的网址

配置Scrapy工程

由于为Scrapyd添加了权限管控,因此在12.2.1小节中涉及的部署爬虫、启动/停止爬虫的地方都要做一些小修改。

首先是部署爬虫,为了让scrapyd-deploy能成功地输入密码,需要修改爬虫根目录的scrapy.cfg文件,添加username和password两项,其中username对应账号,password对应密码。

配置好scrapy.cfg以后,部署爬虫的命令不变,还是进入这个Scrapy工程的根目录,执行以下代码即可:

scrapyd-deploy

使用curl启动/关闭爬虫,只需要在命令上加上账号参数即可。账号参数为“-u 用户名:密码”。

所以,启动爬虫的命令为:

curl http://45.76.110.210:6800/schedule.json -d project=工程名 -d spider=爬虫名 -u kingname:genius

停止爬虫的命令为:

```shell
curl http://45.76.110.210:6800/cancel.json -d project=工程名 -d job=爬虫JOBID-u kingname:genius

如果使用Python与requests编写脚本来控制爬虫,那么账号信息可以作为POST方法的一个参数,参数名为auth,值为一个元组,元组第0项为账号,第1项为密码:

result = requests.post(start_url, data=start_data, auth=('kingname', 'genius')).text
result = requests.post(end_url, data=end_data, auth=('kingname', 'genius')).text

分布式架构介绍

在前面章节中已经讲到了把目标放到Redis里面,然后让多个爬虫从Redis中读取目标再爬取的架构,这其实就是一种主—从式的分布式架构。使用Scrapy,配合scrapy_redis,再加上Redis,也就实现了一个所谓的分布式爬虫。

实际上,这种分布式爬虫的核心概念就是一个中心结点,也叫Master。它上面跑着一个Redis,所有的待爬网站的网址都在里面。其他云服务器上面的爬虫(Slave)就从这个共同的Redis中读取待爬网址。

分布式爬虫架构图

只要能访问这个Master服务器,并读取Redis,那么其他服务器使用什么系统什么提供商都没有关系。

例如,使用Ubuntu作为爬虫的Master,用来做任务的派分。

使用树莓派、Windows 10 PC和Mac来作为分布式爬虫的Slave,用来爬取网站,并将结果保存到Mac上面运行的MongoDB中。

其中,作为Master的Ubuntu服务器仅需要安装Redis即可,它的作用仅仅是作为一个待爬网址的临时中转,所以甚至不需要安装Python。

在Mac、树莓派和Windows PC中,需要安装好Scrapy,并通过Scrapyd管理爬虫。由于爬虫会一直监控Master的Redis,所以在Redis没有数据的时候爬虫处于待命状态。

当目标被放进了Redis以后,爬虫就能开始运行了。由于Redis是一个单线程的数据库,因此不会出现多个爬虫拿到同一个网址的情况。

如何选择Master

严格来讲,Master只需要能运行Redis并且能被其他爬虫访问即可。但是如果能拥有一个公网IP则更好。这样可以从世界上任何一个能访问互联网的地方访问Master。但如果实在没有云服务器,也并不是说一定得花钱买一个,如果自己有很多台计算机,完全可以用一台计算机来作为Master,其他计算机来做Slave。

Master也可以同时是Slave。在第11章的例子中,Scrapy和Redis是安装在同一台计算机中的。这台计算机既是Master又是Slave。

Master一定要能够被其他所有的Slave访问。所以,如果所有计算机不在同一个局域网,那么就需要想办法弄到一台具有公网IP的计算机或者云服务器。

在中国,大部分情况下,电信运营商分配到的IP是内网IP。在这种情况下,即使知道了IP地址,也没有办法从外面连进来。

在局域网里面,因为局域网共用一个出口,局域网内的所有共用同一个公网IP。对网站来说,这个IP地址访问频率太高了,肯定不是人在访问,从而被网站封锁的可能性增大。而使用分布式爬虫,不仅仅是为了提高访问抓取速度,更重要的是降低每一个IP的访问频率,使网站误认为这是人在访问。所以,如果所有的爬虫确实都在同一个局域网共用一个出口的话,建议为每个爬虫加上代理。

在实际生产环境中,最理想的情况是每一个Slave的公网IP都不一样,这样才能做到既能快速抓取,又能减小被反爬虫机制封锁的机会。

小结

本章主要介绍了Scrapy中间件的高级用法和爬虫的批量部署。

使用中间件可以让爬虫专注于提取数据,而像更换IP、获取登录Session等事情全部都交给中间件来做。这样可以让爬虫本身的代码更加简洁,也便于协同开发。

使用Scrapyd来部署爬虫,可以实现远程开关爬虫和批量部署爬虫,从而实现分布式爬虫。

第13章 爬虫开发中的法律和道德问题

小结

  • 在爬虫开发和数据采集的过程中,阅读网站的协议可以有效发现并规避潜在的法律风险。
  • 爬虫在爬取网站的时候控制频率,善待网站,才能让爬虫运行得更长久。

你可能感兴趣的:(编程语言,开发框架,Python,技术)