1.Scrapy框架的介绍
scrapy是使用python编写的为了爬取网站数据,提取结构性数据而编写的一个应用框架,用途十分广泛。它是基于Twisted的一个异步处理框架,拥有架构清晰,可扩展性强,可灵活完成各种需求,各模块之间的耦合程度低,只需简单的几个模块就可以实现一个爬虫,因此上手很快。
1.1 Scrapy工作机制
大体上可以将Scrapy中的模块分为下面几个:引擎(Scrapy Engine),调度器(Schedule),爬虫主件(Spider),下载器(Downloader),管道(ItemPipeline)。各模块之间相互配合,由此构成了一个框架的基本结构。
框架的部分功能如下:
引擎(Scrapy Engine),处理整个系统的数据流,出发事务,是整个框架的核心
调度器(Schedule),接受引擎发过来的请求,并将其加入队列中,在引擎再次请求时将请求提供给引擎。
爬虫主件(Spider),蜘蛛,在其内定义了爬取的逻辑以及网页的解析规则,主要是解析响应并提取数据和提供新的url
下载器(Downloader),只有一个功能,就是发送请求获得响应
管道(ItemPipeline),处理蜘蛛从网页中提取的数据,主要任务是清洗,验证与存储数据。可以选择将数据存在文件或数据库中。
中间件(MiddleWare),有下载中间件以及爬虫中间件,其中在下载中间件中,我们可以设置user-agent,IP代理,cookie等以获得信息。
1.2 Scrapy数据流向
在scrapy运行之后,它的数据流向大概是下面这个样子:
- Engine首先打开一个网站,找到处理该网站的Spider,并向该Spider请求第-个要爬取的URL。
- Engine从Spider中获取到第一个要爬取的URL,并通过Scheduler以Request的形式调度。
- Engine向Scheduler请求下一个要爬取的URL。
- Sceduler返回下一个要爬取的 URL给Engine, Engine 将URL通过Downloader Middlewares转发给Downloader下载。
- 一旦页面下载完毕,Downloader生成该页面的Response,并将其通过Downloader
Middlewares发送给Engine。 - Engine从下载器中接收到Response,并将其通过Spider Middlewares发送给Spider处理。
- Spider处理Response,并返回爬取到的Item及新的Request给Engine。
- Engine将Spider返回的Item给Item Pipeline,将新的Request给Scheduler。
- 重复第(2)步到第(8)步,直到Scheduler中没有更多的Request, Engine 关闭该网站,爬取结束。
通过多个组件的相互协作、不同组件完成工作的不同、组件对异步处理的支持,Scrapy 最大限度地利用了网络带宽,大大提高了数据爬取和处理的效率。
1.3 scrapy制作步骤
制作一个scrapy爬虫一共需要4步,
- 新建项目
- 明确目标
- 制作爬虫
- 存储内容
1.4 scrapy安装介绍
Scrapy 框架官方网址: http://doc.scrapy.org/en/latest
Scrapy 中文维护站点: http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html
Windows 安装方式:
通过 pip 安装 Scrapy 框架 pip install Scrapy
Ubuntu 需要 9.10 或以上版本安装方式:
安装非 Python 的依赖:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev
zlib1g-dev libffi-dev libssl-dev
通过 pip 安装 Scrapy 框架 sudo pip install scrapy
其他平台的安装可以见:scrapy各平台安装方法
判断是否安装成功,在终端下输入scrapy命令,如果出现下面这种结果,说明已经成功。
安装使用过程中的错误及解决办法:
-
使 用 Scrapy 时 遇 到 0: UserWarning: You do not have a working installation of the
service_identity module: ‘cannot import name ‘opentype’’. Please install it from … 的 问 题 解 决网址:http://www.bubuko.com/infodetail-2467560.html -
报错cannot import name “etree”
原因是因为lxml版本过高,需卸载后重新安装3.8及以下的版本 -
报错:Microsoft Visual C++ 14.0 is required.Get it with “Microsoft
Visual C++ Build Tools”…
解决办法:前往http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 下载 twisted 对应版本的 whl 文件( 如我的 Twisted-18.4.0-cp36-cp36m-win_amd64.whl),cp 后面是 python 版本, amd64 代表 64 位, 下载完毕后运行命令:pip install C:\Users\ibm\Downloads\Twisted-18.4.0-cp36-cp36m-win_amd64.whl
-
报错:No module named ‘win32api’
解决办法:pip install pywin32
2.Scrapy框架的使用
在scrapy使用过程中,项目的创建,执行均在终端进行,IDE只是一个编写代码的地方,不再运行scrapy项目。
创建项目时,过程如下(以百度为例)
打开终端:
scrapy startproject baidu
cd demoname
scrapy genspider baidu baidu.com
在这里介绍一下各文件的功能:
- scrapy.cfg:它是Scrapy项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。
- items.py:它定义Item数据结构,所有的Item的定义都可以放这里。
- pipelines.py:它定义Item Pipeline的实现,比如数据的处理与保存。
- settings.py:它定义项目的全局配置。
- middlewares.py:它定义Spider Middlewares和Downloader Middlewares的实现。
- spiders包:其内包含一个个Spider的实现,每个Spider都有单独的一个文件。
打开项目中的settings文件,将是否遵守爬虫协议改为False
在修改过爬虫协议之后,已经可以从目标服务器获取关于网页的响应信息了。不信可以将response写入文件中试一试。获取响应内容要是用response.body
或者response.text
方式。
如:
可以看到start_urls为列表,在该列表中存储了需要爬取的网页的url,如果需要爬取的网页很少时可以选择直接将多个url存储在该列表。如果url规律可循可使用循环或者列表推导式来存储url。
拿到了网页的内容之后就是对网页内容的提取了,在scrapy中有自己的数据提取方法:Selector(选择器),它是基于lxml来构建的,支持xpath选择器以及css选择器,正则表达式,功能全面,解析速度很快,精确度也很高。当然在框架外也是可以使用Selector选择器的,这样提取数据的方法又多了一种。
由于selector支持xpath,所以在拿到响应后,不需要做其他的处理就可以直接用xpath中的方法来提取结构化数据。而且在上面也讲过,在提取数据的时候有两部分结果,一个是需要交给管道进行下一步处理的数据,另一个是需要进一步爬取的网站的url。因为只有一个蜘蛛在进行工作,所以在这里就用到了生成器的相关知识,如果不熟悉,可以去找基础知识翻看。一般情况下,对这种情况我们是这样处理的,使用多个yield,一步一步的完成网页的爬取与数据提取。而对于数据的存储,则需要创建一个item对象,由它将数据携带给管道进行处理。
比如下面这个例子(爬取golang贴吧):
extract_first()是将匹配的第一个结果取出来,如果没有取到,也不会报错;
extract()是将匹配的全部结果以列表形式返回,仍需索引值来取出数据;
response.urljoin()可以将相对路径转为绝对路径,很好用,不用费劲的去拼接url,一个不小心还会拼错;
response.url返回的是当前的url。
spider包中的spider.py文件的代码:
class GolangSpider(scrapy.Spider):
name = 'golang'
allowed_domains = ['baidu.com']
start_urls = ['https://tieba.baidu.com/f?kw=golang&ie=utf-8&pn=0']
def parse(self, response):
print('*****************************'*100,response.url)
li_list = response.xpath(r'.//li[@class=" j_thread_list clearfix"]')
for li in li_list:
item = GolangSpiderItem()
title = li.xpath(r'.//a[@class="j_th_tit "]/text()').extract_first()
item['title']=title
author = li.xpath(r'.//span[@class="frs-author-name-wrap"]/a/text()').extract_first()
item['author'] = author
time = li.xpath(r'.//span[@class="pull-right is_show_create_time"]/text()').extract_first().strip()
item['time'] = time
# href = 'https://tieba.baidu.com'+li.xpath(r'.//a[@class="j_th_tit "]/@href').extract_first()
href = response.urljoin(li.xpath(r'.//a[@class="j_th_tit "]/@href').extract_first())
yield scrapy.Request(url=href,callback=self.parser2,meta={'data':item})
next_page = response.xpath('.//div[@id="frs_list_pager"]/a[@class="next pagination-item "]/@href')
if next_page:
next_page = response.urljoin(next_page.extract_first())
yield scrapy.Request(url=next_page,callback=self.parse)
def parser2(self,contents):
print('进入了:',contents.url,'页面')
item = contents.meta.get('data')
content = contents.xpath('.//div[@class="d_post_content j_d_post_content "]/text()').extract()
i = ''
n = 1
for l in content:
floor = '第{}楼:'.format(n)
i = i + floor+l.strip()
n += 1
item['content'] = i
print(contents.url)
yield item
在items.py文件中,items.py中定义了各爬虫相对应的Item类,继承scrapy.Item类,每一个字段都是scrapy.Field()的对象。
在pipelines.py文件中定义了关于数据的处理方式。到底是丢弃还是去重还是保存。
在使用之前先要在settings.py文件中将有关item pipeline的设置打开。
注意在这里key值由三个部分组成,包名,文件名,类名。不要写错,value值为数字,在1-1000之间取值,值越小优先级越高。
pipelines.py文件中对数据的处理可以见下面的代码:
如果是写入文件中有下面两种写法
第一种方法:
class GolangSpiderPipeline(object):
def __init__(self):
os.remove('baidu.json')
self.file = open('baidu.json','a',encoding='utf-8')
def process_item(self,item,spider):
str = json.dumps(dict(item),ensure_ascii=False)+'\n'
self.file.write(str)
# json.dump(dict(item),open('tieba.json','a',encoding='utf-8'),ensure_ascii=False)
return item
def close_spider(self,spider):
self.file.close()
第二种方法:
class GolangSpiderPipeline(object):
def process_item(self,item,spider):
fp = open('tieba.json', 'a', encoding='utf-8')
json.dump(dict(item),fp,ensure_ascii=False)
fp.write('\n')
return item
如果是存入数据库可以见下面的代码(以MongoDB为例):
class GolangSpiderPipeline(object):
def __init__(self):
# 连接数据库,库和表如果没有会自动创建
self.client = pymongo.MongoClient('localhost', 27017)
# 选择库名
self.db = self.client['Golang']
# 查询数据db.baidu.find()
# 使用数据库use dbname
def process_item(self,item,spider):
# baidu为库中的表名
self.db.baidu.insert(dict(item))
return item
def close_spider(self,spider):
self.client.close()
到此,一个完整的爬虫框架已经结束了,在没有反爬措施的网站中,是完全可以提取到我们想要的各种信息的。
3.Logging
Scrapy 提供了 log 功能, 可以通过 logging 模块使用。
可以修改配置文件 settings.py, 任意位置添加下面两行, 效果会清爽很多。
LOG_FILE = "baidu.log"
LOG_LEVEL = "INFO"
Log levels
Scrapy 提供 5 层 logging 级别:
CRITICAL - 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)
设置了级别之后,该日志文件便只会记录此等级及以上的错误日志。
logging 设置
通过在 setting.py 中进行以下设置可以被用来配置 logging:
LOG_ENABLED 默认: True, 启用 logging
LOG_ENCODING 默认: ‘utf-8’, logging 使用的编码
LOG_FILE 默认: None, 在当前目录里创建 logging 输出文件的文件名
LOG_LEVEL 默认: ‘DEBUG’, log 的最低级别
LOG_STDOUT 默认: False 如果为 True, 进程所有的标准输出(及错误)将会被重定向到
log 中。 例如, 执行 print “hello” , 其将会在 Scrapy log 中显示。