目录
八、协程
1、多协程的用法gevent
2、queue模块
九、Scrapy框架
1、Scrapy的结构
2、Scrapy安装
3、Scrapy使用流程
十、爬虫进阶方向与反爬虫
1、爬虫进阶方向
2、反爬虫
前几章讲了python爬虫基础操作步骤:
获取数据解析数据提取数据存储数据定时邮件前几章爬取的网页所涉及的数据量不大,但是当我们要爬取成千上万的数据时,因为要一行行一次执行,我们要等很久才能拿到想要的数据。那么我们就需要让多个爬虫一起爬取,并发执行提高爬取的效率。它的原理是:一个任务在执行的过程中,如果遇到等待,先去执行其他的任务,当等待结束,再回来继续执行之前的任务,在计算机的世界,这种任务来回切换得非常快速,看上去就像多个任务被同时执行一样。这种方式就叫多协程,是一种非抢占的异步技术。使用多协程,就能让多个爬取任务用异步的方式交替执行。
在python中,可以使用gevent库实现多协程。这是一个轻量级协同程序。
gevent库安装: windows环境:pip install gevent
gevent实现多协程爬取的步骤:
定义爬取函数用gevent.spawn()创建任务用gevent.joinall()执行任务参考资料:
gevent官方文档:http://www.gevent.org/contents.html
gevent中文参考文档:https://github.com/panguangyu/gevent-tutorial-chinese
gevent使用示例:
# Description:使用多协程爬取8个网页 from gevent import monkey #从gevent库里导入monkey模块。 monkey.patch_all() #monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。 import gevent,time,requests #导入gevent、time、requests。 start = time.time() #记录程序开始时间。 url_list = ['https://www.baidu.com/', 'https://www.sina.com.cn/', 'http://www.sohu.com/', 'https://www.qq.com/', 'https://www.163.com/', 'http://www.iqiyi.com/', 'https://www.tmall.com/', 'http://www.ifeng.com/'] #把8个网站封装成列表。 def crawler(url): #定义一个crawler()函数。 r = requests.get(url) #用requests.get()函数爬取网站。 print(url,time.time()-start,r.status_code) #打印网址、请求运行时间、状态码。 tasks_list = [ ] #创建空的任务列表。 for url in url_list: #遍历url_list。 task = gevent.spawn(crawler,url) #用gevent.spawn()函数创建任务。 tasks_list.append(task) #往任务列表添加任务。 gevent.joinall(tasks_list) #执行任务列表里的所有任务,就是让爬虫开始爬取网站。 end = time.time() #记录程序结束时间。 print(end-start) #打印程序最终所需时间。第1、3行代码:从gevent库里导入了monkey模块,这个模块能将程序转换成可异步的程序。monkey.patch_all(),它的作用其实就像你的电脑有时会弹出“是否要用补丁修补漏洞或更新”一样。它能给程序打上补丁,让程序变成是异步模式,而不是同步模式。它也叫“猴子补丁”。我们要在导入其他库和模块前,先把monkey模块导入进来,并运行monkey.patch_all()。这样,才能先给程序打上补丁。
第21、23、25行代码:我们定义了一个crawler函数,只要调用这个函数,它就会执行【用requests.get()爬取网站】和【打印网址、请求运行时间、状态码】这两个任务
第33行代码:因为gevent只能处理gevent的任务对象,不能直接调用普通函数,所以需要借助gevent.spawn()来创建任务对象。gevent.spawn()的参数需为要调用的函数名及该函数的参数。比如,gevent.spawn(crawler,url)就是创建一个执行crawler函数的任务,参数为crawler函数名和它自身的参数url。
第37行代码:调用gevent库里的joinall方法,能启动执行所有的任务。gevent.joinall(tasks_list)就是执行tasks_list这个任务列表里的所有任务,开始爬取。
上面的示例中我们用多协程爬取的是8个网站,如果要爬取更多的网站,创建大量的任务时,我们可以借助queue模块。
queue就是队列,我们可以用queue模块来存储任务,让任务变成一条整机的队列,就像银行窗口排队的做法。其实queue就是一种有序的数据结构,可以用来存取数据。
这样,协程就可以从队列里把任务提取出来执行,知道队列孔了,任务也就处理完了。
queue对象的方法 put_nowait()往队列里存储数据get_nowait()从队列里提取数据empty()判断队列是否为空full()判断队列是否为满
qsize()判断队列还剩多少数量用queue模块的过程:
用Queue()创建队列用put_nowait()存储数据用get_nowait()提取数据用empty()为判断依据循环执行任务queue使用示例:
# Description:多协程,使用gevent,queue模块爬取网址数据 from gevent import monkey #从gevent库里导入monkey模块。 monkey.patch_all() #monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。 import gevent,time,requests #导入gevent、time、requests from gevent.queue import Queue #从gevent库里导入queue模块 start = time.time() url_list = ['https://www.baidu.com/', 'https://www.sina.com.cn/', 'http://www.sohu.com/', 'https://www.qq.com/', 'https://www.163.com/', 'http://www.iqiyi.com/', 'https://www.tmall.com/', 'http://www.ifeng.com/'] work = Queue() #创建队列对象,并赋值给work。 for url in url_list: #遍历url_list work.put_nowait(url) #用put_nowait()函数可以把网址都放进队列里。 def crawler(): while not work.empty(): #当队列不是空的时候,就执行下面的程序。 url = work.get_nowait() #用get_nowait()函数可以把队列里的网址都取出。 r = requests.get(url) #用requests.get()函数抓取网址。 print(url,work.qsize(),r.status_code) #打印网址、队列长度、抓取请求的状态码。 tasks_list = [ ] #创建空的任务列表 for x in range(2): #相当于创建了2个爬虫 task = gevent.spawn(crawler) #用gevent.spawn()函数创建执行crawler()函数的任务。 tasks_list.append(task) #往任务列表添加任务。 gevent.joinall(tasks_list) #用gevent.joinall方法,执行任务列表里的所有任务,就是让爬虫开始爬取网站。 end = time.time() print(end-start)拓展知识:
我们的CPU已经有单核进化为了多核,每个核都能够独立运作。计算机开始能够真正意义上同时执行多个任务(术语叫并行执行),而不是在多个任务之间来回切换(术语叫并发执行)。
我们电脑一般都会是多核CPU。多协程,其实只占用了CPU的一个核运行,没有充分利用到其他核。利用CPU的多个核同时执行任务的技术,我们把它叫做“多进程”。
所以,真正大型的爬虫程序不会单单只靠多协程来提升爬取速度的。比如,百度搜索引擎,可以说是超大型的爬虫程序,它除了靠多协程,一定还会靠多进程,甚至是分布式爬虫。
进程、线程、协程之间的关系:协程在本质上只用到CPU的一个核。而多进程(multiprocessing库)爬虫允许你使用CPU的多个核。可以这么理解,一个核代表一个进程,而一个核即一个进程里面又有多个线程,同时,一个线程里面可以有多个协程。
线程是进程的子单位,都是实体;而协程从严格意义来讲,是组织好的代码流程, 协程需要线程来承载运行。多协程的爬取一般只用到一个进程的一个线程单位。
Scrapy是一套基于Twisted的异步处理框架,纯python实现的爬虫框架。在我们之前的爬虫基础中,我们要爬取需要的数据,需要按照爬虫操作流程导入和操作不同的模块。但Scrapy框架这些都自动实现了,我们可以根据需求定制开发几个模块,就能够轻松的实现一个爬虫。
Scrapy的结构如下图所示:
在Scrapy框架中:
Scrapy Engine(引擎)是中心,其他组成部分由引擎调度。
Scheduler(调度器):主要负责处理引擎发送过来的requests对象(即网页请求的相关信息集合,包括params,data,cookies,request headers…等),会把请求的url以有序的方式排列成队,并等待引擎来提取(功能上类似于gevent库的queue模块)。
Downloader(下载器):是负责处理引擎发送过来的requests,进行网页爬取,并将返回的response(爬取到的内容)交给引擎。它对应的是爬虫流程【获取数据】这一步。
Spiders(爬虫):主要任务是创建requests对象和接受引擎发送过来的response(Downloader部门爬取到的内容),从中解析并提取出有用的数据。它对应的是爬虫流程【解析数据】和【提取数据】这两步。
Item Pipeline(数据管道):只负责存储和处理Spiders部门提取到的有用数据。这个对应的是爬虫流程【存储数据】这一步。
Downloader Middlewares(下载中间件):会提前对引擎发送的诸多requests做出处理。
Spider Middlewares(爬虫中间件):会提前接收并处理引擎发送来的response,过滤掉一些重复无用的东西。
Scrapy Engine负责数据和信号在不同模块之间的传递scrapy已实现Scheduler一个存放引擎发过来的requests请求的队列scrapy已实现Downloader下载引擎发送过来的请求,并返回给引擎scrapy已实现Spider处理引擎发送过来的response,并返回给引擎需要自己实现Item Pipeline处理引擎发送过来的数据,比如存储到excel表格需要自己实现Downloader Middlewares可以自定义下载,设置代理,设置请求头等根据自己的需要实现Spider MiddlewaresSpider可以自定义requests请求和response过滤根据自己的需要实现在Scrapy里,整个爬虫程序的流程都不需要我们去操心,且Scrapy中的程序全部都是异步模式,所有的请求或返回的响应都由引擎自动分配去处理。
哪怕有某个请求出现异常,程序也会做异常处理,跳过报错的请求,继续往下运行程序。
详情见官方参考文档:https://doc.scrapy.org/en/latest/
Scrapy安装方法与python版本有关,如果python版本为python3.8以下版本,直接用常规pip安装方式:
windows: pip install scrapy
mac: pip3 install scrapy
如果python版本为python3.8则会报错,博客上也有详细Window10环境下的安装指南,详见下面链接:
//blog.csdn.net/Boy_Teacher/article/details/106537835
以爬取豆瓣Top250书籍信息(包含书名、出版信息、书籍评分)为例。https://book.douban.com/top250
1)创建项目
要在本地电脑打开终端cd xxx 进入要保存项目的目录下输入创建项目命令:python -m scrapy startproject douban(爬虫名)即创建如下scrapy项目结构:
2)定义数据
定义数据是在items.py中,当我们每一次要记录数据的时候,都要记录“书名”,“出版信息”,“评分”。我们会实例化一个对象,利用这个对象来记录数据。每一次,当数据完成记录,它会离开spiders,来到Scrapy Engine(引擎),引擎将它送入Item Pipeline(数据管道)处理。
import scrapy #导入scrapy class DoubanItem(scrapy.Item): #定义一个类DoubanItem,它继承自scrapy.Item title = scrapy.Field() #定义书名的数据属性 publish = scrapy.Field() #定义出版信息的数据属性 score = scrapy.Field() #定义评分的数据属性
第3行代码:我们定义了一个DoubanItem类。它继承自scrapy.Item类。
第5、7、9行代码:我们定义了书名、出版信息和评分三种数据。scrapy.Field()这行代码实现的是,让数据能以类似字典的形式记录。
3)定义爬虫
Scrapy中爬虫是在spiders目录下。我们可以在spiders这个文件夹里创建爬虫文件。我们新建一个爬虫文件,并把这个文件命名为top250.py。那么爬虫代码都需要在这个top250.py里编写。
top250.py中代码如下所示:
import scrapy import bs4 from ..items import DoubanItem # 需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。 class DoubanSpider(scrapy.Spider): #定义一个爬虫类DoubanSpider。 name = 'douban' #定义爬虫的名字为douban。 allowed_domains = ['book.douban.com'] #定义爬虫爬取网址的域名。 start_urls = [] #定义起始网址。 for x in range(3): url = 'https://book.douban.com/top250?start=' + str(x * 25) start_urls.append(url) #把豆瓣Top250图书的前3页网址添加进start_urls。 def parse(self, response): #parse是默认处理response的方法。 bs = bs4.BeautifulSoup(response.text,'html.parser') #用BeautifulSoup解析response。 datas = bs.find_all('tr',class_="item") #用find_all提取<tr class="item">元素,这个元素里含有书籍信息。 for data in datas: #遍历data。 item = DoubanItem() #实例化DoubanItem这个类。 item['title'] = data.find_all('a')[1]['title'] #提取出书名,并把这个数据放回DoubanItem类的title属性里。 item['publish'] = data.find('p',class_='pl').text #提取出出版信息,并把这个数据放回DoubanItem类的publish里。 item['score'] = data.find('span',class_='rating_nums').text #提取出评分,并把这个数据放回DoubanItem类的score属性里。 print(item['title']) #打印书名。 yield item #yield item是把获得的item传递给引擎。代码中定义了一个爬虫类DoubanSpider(Douban是项目名)。DoubanSpider类继承自scrapy.Spider类。
name是定义爬虫的名字,这个名字是爬虫的唯一标识。name = 'douban'意思是定义爬虫的名字为douban。等会我们启动爬虫的时候,要用到这个名字。
allowed_domains是定义允许爬虫爬取的网址域名(不需要加https://)。如果网址的域名不在这个列表里,就会被过滤掉。allowed_domains就限制了,我们这种关联爬取的URL,一定在book.douban.com这个域名之下,不会跳转到某个奇怪的广告页面。
start_urls是定义起始网址,就是爬虫从哪个网址开始抓取。在此,allowed_domains的设定对start_urls里的网址不会有影响。后续关联网址如果不在allowed_domains里,则不会被爬取。
parse是Scrapy里默认处理response的一个方法,爬取解析网页数据。网页请求requests.get()这一步已经由Scrapy框架自动完成。
4)设置
我们用代码编写好了一个爬虫。不过,实际运行的话,可能还是会报错。原因在于Scrapy里的默认设置没被修改。比如我们需要修改请求头。点击settings.py文件,你能在里面找到如下的默认设置代码:
# Crawl responsibly by identifying yourself (and your website) on the user-agent #USER_AGENT = 'douban (+http://www.yourdomain.com)' # Obey robots.txt rules ROBOTSTXT_OBEY = True把USER _AGENT的注释取消(删除#),然后替换掉user-agent的内容,就是修改了请求头。
又因为Scrapy是遵守robots协议的,如果是robots协议禁止爬取的内容,Scrapy也会默认不去爬取,所以我们还得修改Scrapy中的默认设置。把ROBOTSTXT_OBEY=True改成ROBOTSTXT_OBEY=False,就是把遵守robots协议换成无需遵从robots协议,这样Scrapy就能不受限制地运行。
关于设置的详细说明请见如下文档:
https://blog.csdn.net/u011781521/article/details/70188171
5)存储
一、存储为CSV格式
只需在settings.py文件里,添加如下的代码即可。
FEED_URI='./storage/data/%(name)s.csv' FEED_FORMAT='CSV' FEED_EXPORT_ENCODING='ansi'FEED_URI是导出文件的路径。'./storage/data/%(name)s.csv',就是把存储的文件放到与main.py文件同级的storage文件夹的data子文件夹里。
FEED_FORMAT 是导出数据格式,写CSV就能得到CSV格式。
FEED_EXPORT_ENCODING 是导出文件编码,ansi是一种在windows上的编码格式,你也可以把它变成utf-8用在mac电脑上。
二、存储为excel表格
我们使用openpyxl模块。
我们需要先在settings.py里设置启用ITEM_PIPELINES,设置方法如下:只要取消ITEM_PIPELINES的注释(删掉#)即可。
#需要修改`ITEM_PIPELINES`的设置代码: # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html #ITEM_PIPELINES = { # 'jobui.pipelines.jobuiPipeline': 300, # }
接着,我们就可以去编辑pipelines.py文件。
import openpyxl class DoubanPipeline(object): #定义一个DoubanPipeline类,负责处理item def __init__(self): #初始化函数 当类实例化时这个方法会自启动 self.wb =openpyxl.Workbook() #创建工作薄 self.ws = self.wb.active #定位活动表 self.ws.append(['书名', '出版信息', '评分']) #用append函数往表格添加表头 def process_item(self, item, spider): #process_item是默认的处理item的方法,就像parse是默认处理response的方法 line = [item['title'], item['publish'], item['score']] #把书名、出版信息和评分都写成列表的形式,赋值给line self.ws.append(line) #用append函数把书名、出版信息和评分的数据都添加进表格 return item #将item丢回给引擎,如果后面还有这个item需要经过的itempipeline,引擎会自己调度 def close_spider(self, spider): #close_spider是当爬虫结束运行时,这个方法就会执行 self.wb.save('./DoubanTop250.xlsx') #保存文件 self.wb.close() #关闭文件
6)运行
方法一:
在本地电脑的终端跳转到scrapy项目的文件夹(跳转方法:cd+文件夹的路径名),
然后输入命令行:python -m scrapy crawl douban(douban 就是我们爬虫的名字)。
方法二:
在最外层的大文件夹里新建一个main.py文件(与scrapy.cfg同级)。在main.py文件里输入以下代码:
from scrapy import cmdline import sys,os dirpath=os.path.dirname(os.path.abspath(__file__)) print(dirpath) sys.path.append(dirpath) os.chdir(dirpath) cmdline.execute(['scrapy','crawl','jobui'])在cmdline模块中,有一个execute方法能执行终端的命令行,不过这个方法需要传入列表的参数。我们想输入运行Scrapy的代码scrapy crawl douban,就需要写成['scrapy','crawl','douban']这样。
现在学会了爬虫的基础操作,爬虫学习进阶可以考虑从下面几个方面做提升:
1)解析与提取
爬虫解析模块除了基础操作中用到的BeautifulSoup解析、Selenium自带解析库,此外还会有xpath、lxml等。
数据的解析提取中有个很强大的工具正则表达式(re模块),它可以让我们自己设定一套复杂的规则,然后把目标文本里符合条件的相关内容给找出来。
2)存储
我们目前已经使用过的是csv和excel。它们并不是非常高难度的模块,可以去翻阅它们的官方文档,了解更多的用法。
当数据量变得十分巨大,同时数据与数据之间的关系,应难以用一张简单的二维平面表格来承载。那么,我们需要数据库的帮助。学习数据库,需要去接触另一种语言:SQL。
3)数据分析与可视化
可以学习了解模块与库:Pandas/Matplotlib/Numpy/Scikit-Learn/Scipy。进行数据分析与可视化相关的操作。
4)更多的爬虫
当有太多的数据要爬取,就要开始关心爬虫的速度。我们现在只涉及到了协程。协程在本质上只用到CPU的一个核。而多进程(multiprocessing库)爬虫允许我们使用CPU的多个核,所以我们可以使用多进程,或者是多进程与多协程结合的方式进一步优化爬虫。
可以去了解分布式爬虫相关的知识,我们去创建一个共享的队列,队列里塞满了待执行的爬虫任务。让多个设备从这个共享队列当中,去获取任务,并完成执行。
我们已经简单地学过Scrapy框架的基本原理和用法。而一些更深入的用法:使用Scrapy模拟登录、存储数据库、使用HTTP代理、分布式爬虫……
先去更深入地学习、了解Scrapy框架。然后再了解一些其他的优秀框架,如:PySpider。
5)更多的项目实践
1)请求头
2)限制登录
3)复杂的交互,如验证码
4)IP限制
......