文章目录
爬虫系列文章:
今天来介绍一下 Python
的一个爬虫框架Scrapy
,类似的还有 Portia
Crawley
。
本项目源码地址:https://github.com/wsuo/pythonHomework/tree/master/demo
一、Scrapy 框架准备
首先是安装爬虫这个框架:
pip install scrapy
官方文档:https://docs.scrapy.org/en/latest/
如果是 Windows 下安装的,还需要安装一下这个:
pip install pypiwin32
如果是 Ubuntu
上安装,需要提前安装这个:
sudo apt-get install python3-dev build-essential python3-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
二、快速启动项目
以爬取糗事百科网站为例:https://www.qiushibaike.com/
可以分为以下四步:
scrapy startproject demo
cd demo
scrapy genspider qsbk qiushibaike.com
scrapy crawl qsbk
1、创建项目结构
创建一个名为 demo
的项目,该命令仅仅是创建项目结构,你完全可以自己创建,只不过自动生成方便一些:
scrapy startproject demo
使用 PyCharm
打开一个空的文件夹,然后进入终端输入命令即可:
项目结构如图:
items.py
:用来存放爬虫爬取下来数据的模型。middlewares.py
:用来存放各种中间件的文件。pipelines.py
:用来将items
的模型存储到本地磁盘中。settings.py
:本爬虫的一些配置信息(比如请求头、多久发送一次请求、ip***池等)。scrapy.cfg
:项目的配置文件。spiders
包:以后所有的爬虫,都是存放到这个里面。
2、创建爬虫
先执行进入到工作目录:
cd demo
然后执行命令创建爬虫:
scrapy genspider qsbk qiushibaike.com
然后就会自动生成一个文件qsbk.py
:
3、更改设置
首先我们要更改一下设置项,为了能够爬到我们想要的数据,我们这里 顶风作案
一下:
修改 ROBOTSTXT_OBEY = False
;
然后加上请求头的 User-Agent
,伪装为浏览器访问:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.116 Safari/537.36',
}
4、爬虫类分析
下面我们来看一下爬虫类:
name = 'qsbk' # 运行的时候输入这个名字,比如
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['http://qiushibaike.com/'] # 开始的 url
我们先来试一下:
class QsbkSpider(scrapy.Spider):
name = 'qsbk' # 运行的时候输入这个名字
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['https://www.qiushibaike.com/text/page/1/'] # 开始的 url
def parse(self, response):
print('-'*40)
print(response)
print(type(response))
print('-'*40)
运行一下:scrapy crawl qsbk
就可以看到结果了。
<200 https://www.qiushibaike.com/text/page/1/>
<class 'scrapy.http.response.html.HtmlResponse'>
类型为 HtmlResponse
。
追踪这个类的父类:
可以看到有 xpath
的方法,说明我们可以使用 xpath
解析响应信息。
编写如下代码:
def parse(self, response):
content = response.xpath('//div[@class="content"]')
print('-'*40)
print(response)
print(type(content))
print(type(response))
print('-'*40)
输出:
追踪类SelectorList
,找到selector
类:
观察该类的方法。
比较重要的方法就是 extract
方法可以将 SelectorList
类型的转化为列表类型,并且里面装的是字符串,extract_first
方法是获取第一个元素。
5、编写启动脚本
由于每次都要输入命令启动挺麻烦的,所以我们可以使用脚本文件执行命令行的命令。
创建一个start.py
文件,随便在哪里创建都可以;
内容如下:
from scrapy import cmdline
cmdline.execute(['scrapy', 'crawl', 'qsbk', '--nolog'])
三、爬虫实战
我们先初步的探索,然后慢慢的优化。
1、初步探索
继续上一步的操作,我们在爬虫类QsbkSpider
中继续写逻辑。
先来获取一下作者的名称小试牛刀一下,编写代码:
class QsbkSpider(scrapy.Spider):
name = 'qsbk' # 运行的时候输入这个名字
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['https://www.qiushibaike.com/text/page/1/'] # 开始的 url
def parse(self, response):
divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in divs:
author = div.xpath('./div[@class="author clearfix"]//h2/text()').get().strip()
print(author)
查看输出结果:
说明这是可以的,下面我们继续爬取后面的内容,比如爬取段子的内容。
content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
yield
的作用是将函数作为一个生成器返回,以后遍历的时候就会把数据一个一个的拿过去
def parse(self, response):
divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in divs:
author = div.xpath('.//h2/text()').get().strip()
content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
# yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
yield {'昵称': author, '段子内容': content}
然后我们可以存储到本地,这就是需要到管道中执行了,所以打开DemoPipeline
类:
class DemoPipeline(object):
def process_item(self, item, spider):
return item
def open_spider(self, spider):
print('爬虫开始')
def close_spider(self, spider):
print('爬虫结束')
后面两个方法默认是没有的,但是如果我们有文件操作最好可以放在这里面。
但是在使用之前,我们必须要先到配置文件中打开管道:
后面这个值越小越先执行。
编写代码:
import json
class DemoPipeline(object):
def process_item(self, item, spider):
json.dump(item, self.file, ensure_ascii=False, indent=4)
self.file.write('\n')
return item
def open_spider(self, spider):
self.file = open('dz.json', 'a', encoding='utf-8')
print('爬虫开始')
def close_spider(self, spider):
self.file.close()
print('爬虫结束')
执行结果:
2、优化数据模型
我们之前使用的是自己造的一个字典在爬虫类和管道之间传输,但是更好的做法是使用数据模型,下面我们来实现一下。
定义一个类DemoItem
:
class DemoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
author = scrapy.Field()
content = scrapy.Field()
然后我们在爬虫类中引入这个模型:
import scrapy
# noinspection PyUnresolvedReferences
from demo.items import DemoItem
class QsbkSpider(scrapy.Spider):
name = 'qsbk' # 运行的时候输入这个名字
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['https://www.qiushibaike.com/text/page/1/'] # 开始的 url
def parse(self, response):
divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in divs:
author = div.xpath('.//h2/text()').get().strip()
content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
# yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
yield DemoItem(author=author, content=content)
好处就是:解耦,约定数据种类,更规范。
但是在管道中获取的时候会有问题,因为他获取的是一个 DemoItem
类型的参数,我们要把它转化为字典类型。
json.dump(dict(item), self.file, ensure_ascii=False, indent=4)
3、优化数据存储方式
优化存储方式肯定要修改管道中的代码,所以我们先注释掉之前写的代码。
使用 scrapy
自带的 JSON
持久化方式。
from scrapy.exporters import JsonItemExporter
class DemoPipeline(object):
def __init__(self):
self.file = open('dz.json', 'wb')
self.exporter = JsonItemExporter(self.file, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
print('爬虫开始了')
# 使用二进制打开
self.exporter.start_exporting()
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()
print('爬虫结束了')
注意这里必须是文件打开的方式必须是 wb
而且不能指定编码格式。
这样执行的结果就是装在一个列表当中了。
它的执行流程其实就是先将数据塞到一个列表当中,然后调用finish_exporting()
写入文件中。
所以他有一个缺陷就是如果 item
比较大的话,他的列表也比较大,一直在内存中就会比较耗内存。
所以我们可以使用JsonLinesItemExporter
这个类,他就是不缓存到一个列表当中,直接一行一行的写入文件。
代码如下:
class DemoPipeline(object):
def __init__(self):
self.file = open('dz.json', 'wb')
self.exporter = JsonLinesItemExporter(self.file, ensure_ascii=False, encoding='utf-8')
def open_spider(self, spider):
print('爬虫开始了')
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self, spider):
self.file.close()
print('爬虫结束了')
结果如下:
还有很多文件格式的保存方法,比如 xml
、csv
、pickel
等等,这里就不再一一演示了。
4、爬取多个页面
我们之前爬的都是单页面,那么怎么才能爬取多页面呢?
我们再来分析一下页面结构:
可以看到最后一个 li
标签中就是页数,但是有一点要注意的就是到最后一页的时候要有一个判断。
我们使用 xpath
来获取 href
的值:
在爬虫类中编写代码:
class QsbkSpider(scrapy.Spider):
name = 'qsbk' # 运行的时候输入这个名字
allowed_domains = ['qiushibaike.com'] # 允许的域名
start_urls = ['https://www.qiushibaike.com/text/page/1/'] # 开始的 url
base_domain = 'https://www.qiushibaike.com'
def parse(self, response):
divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in divs:
author = div.xpath('.//h2/text()').get().strip()
content = ''.join(div.xpath('.//div[@class="content"]/span[1]/text()').getall()).strip()
# yield 的作用是将函数作为一个生成器,以后遍历的时候就会把数据一个一个的拿过去
yield DemoItem(author=author, content=content)
next_url = response.xpath('//ul[@class="pagination"]//li[last()]/a/@href').get()
if not next_url:
return
else:
yield scrapy.Request(self.base_domain + next_url, callback=self.parse)
我们在设置页面设置下载的延时,这样的话可以控制爬的速度,因为太快的话容易被发现,而且还有可能把别人服务器搞垮了:
执行结果:
当然这样不是最好的解决方案,后续文章会讲解 CrawlSpider
类的使用。
参考链接: