title: 爬虫入门三 scrapy
date: 2020-03-14 14:49:00
categories: python
tags: crawler
scrapy框架入门
1 scrapy简介
爬虫框架是实现爬虫功能的一个软件结构和功能组件集合。
官方网站:https://scrapy.org/
Scrapy 0.24 文档: http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html
Requests vs Scrapy
相同点:两者都可以进行页面请求和爬取,Python爬虫的两个重要技术路线两者可用性都好,文档丰富,入门简单两者都没有处理js、提交表单、应对验证码等功能(可扩展)
Requests
页面级爬虫功能库并发性考虑不足,性能较差重点在于页面下载定制灵活上手十分简单
Scrapy
网站级爬虫 框架 并发性好,性能较高 重点在于爬虫结构 一般定制灵活,深度定制困难 入门稍难
2 scrapy 框架,数据流,数据类型
2.1 scrapy框架(5+2)
Spider
(1) 解析Downloader返回的响应(Response) (2) 产生爬取项(scraped item) (3) 产生额外的爬取请求(Request)
Engine
(1)控制所有模块之间的数据流(2)根据条件触发事件
Downloader
根据请求下载网页
Scheduler
对所有爬取请求进行调度管理
Item Pipelines
(1) 以流水线方式处理Spider产生的爬取项 (2) 由一组操作顺序组成,类似流水线 (3) 可能操作包括:清理、检验和查重爬取项中 的HTML数据、将数据存储到数据库
Downloader Middleware
目的:实施Engine、 Scheduler和Downloader 之间进行用户可配置的控制功能:修改、丢弃、新增请求或响应
Spider Middleware
目的:对请求和爬取项的再处理功能:修改、丢弃、新增请求或爬取项
2.2 scrapy数据流
1.Engine从Spider处获得爬取请求(Request)
2.Engine将爬取请求转发给Scheduler,用于调度
3.Engine从Scheduler处获得下一个要爬取的请求
4.Engine将爬取请求通过中间件发送给Downloader
5.爬取网页后,Downloader形成响应(Response)通过中间件发给Engine
6.Engine将收到的响应通过中间件发送给Spider处理
7.Spider处理响应后产生爬取项(scraped Item)和新的爬取请求(Requests)给Engine
8.Engine将爬取项发送给Item Pipeline(框架出口)
9.Engine将爬取请求发送给Scheduler
2.3 scrapy数据类型
Request类
Request对象表示一个HTTP请求,
由Spider生成,由Downloader执行
Response类
Response对象表示一个HTTP响应,
由Downloader生成,由Spider处理
Item类
Item对象表示一个从HTML页面中提取的信息内容
由Spider生成,由Item Pipeline处理
Item类似字典类型,可以按照字典类型操作
3 scrapy安装与使用
3.1 scrapy安装
3.1.1 anoconda安装
conda install scrapy
出现问题/慢,可以添加清华源
3.1.2 pip安装 不推荐
Python 3.5/3.6 下安装scrapy方法:
(1)安装lxml: pip install lxml
(2)下载对应版本的Twisted
http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
pip install D:\Twisted-16.4.1-cp35-cp35m-win_amd64.whl
(下载好的twisted模块的whl文件路径)
(3)安装scrapy:pip install scrapy
(4)安装关联模块pypiwin32:pip install pypiwin32
3.2 scrapy常用链接
3.3 示例
3.3.1 创建项目
命令行输入:
scrapy startproject tutorial
tutorial/
scrapy.cfg
tutorial/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
scrapy.cfg: 项目的配置文件
tutorial/: 该项目的python模块。之后您将在此加入代码
tutorial/items.py: 项目中的item文件.
tutorial/pipelines.py: 项目中的pipelines文件.
tutorial/settings.py: 项目的设置文件.
tutorial/spiders/: 放置spider代码的目录.
3.3.2 定义item
Item 是保存爬取到的数据的容器;其使用方法和python字典类似,并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
编辑 tutorial 目录中的 items.py 文件:
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
Field 对象仅仅是内置的 dict 类的一个别名,并没有提供额外的方法或者属性。换句话说, Field 对象完完全全就是Python字典(dict)。被用来基于类属性(class attribute)的方法来支持 item声明语法 。
3.2.3 编写spider
Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。
为了创建一个Spider,必须继承 scrapy.Spider 类, 且定义以下三个属性:
name: 用于区别Spider。 该名字必须是唯一的,不可以为不同的Spider设定相同的名字。
start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
在项目中生成 spider 文件的两种方法:
命令行输入 Scrapy genspider domain domain.com
tutorial/spiders/目录下创建domain.py
#爬取网页的内容
# -*- coding: utf-8 -*-
import scrapy
class W3schoolSpider(scrapy.Spider):
name = 'w3school'
allowed_domains = ['w3school.com.cn']
start_urls = ['http://www.w3school.com.cn/cssref/css_selectors.asp',
'http://www.w3school.com.cn/xpath/']
def parse(self, response):
filename = response.url.split("/")[-2]
with open(filename, 'wb') as f:
f.write(response.body)
pass
3.2.4 执行spider
进入项目的根目录,执行下列命令启动spider:
scrapy crawl w3school
log包含定义在 start_urls 的初始URL,并且与spider中是一一对应的。在log中可以看到其没有指向其他页面( (referer:None) )。查看当前目录,两个包含url所对应的内容的文件被创建了: cssref, xpath, 正如我们的 parse 方法里做的一样。
Scrapy为Spider的 start_urls 属性中的每个URL创建了 scrapy.Request 对象,并将 parse 方法作为回调函数(callback)赋值给了Request。Request对象经过调度,执行生成 scrapy.http.Response 对象并送回给spider parse() 方法。
3.2.5 提取item Xpath
Scrapy爬虫支持多种HTML信息提取方法:
Beautiful Soup
Lxml
Re
Xpath
CSS
3.2.6 Xpath selector
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
常见的路径表达式如下:
谓语(Predicates)用来查找某个特定的节点或者包含某个指定的值的节点,嵌在方括号[]中。
4 pycharm+anoconda+scrapy
在anaconda按照scrapy,然后打开pycharm,添加anaconda到interpreter。
然后在pycharm的命令行(下面的terminal)输入scrapy startproject xxx [mulu]
然后file,open刚刚创建好的目录,就可以使用模板了
5 建议
第3部分的示例内容比较简略,比如说spider中就没有讲parse的定义参数是因为downloader返回了reponse对象。
比如scrapy crwal w3school 中w3school是domin.py中的name。
详细内容见 https://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html
6 示例 豆瓣电影top250
项目:scrapy startproject douban250
6.1 item.py
import scrapy
class DoubanMovieItem(scrapy.Item):
# 排名
ranking = scrapy.Field()
# 电影名称
movie_name = scrapy.Field()
# 评分
score = scrapy.Field()
# 评论人数
score_num = scrapy.Field()
6.2 DoubanMovieTop250.py (spider)
from scrapy import Request
from scrapy.spiders import Spider
from douban250.items import DoubanMovieItem
class DoubanMovieTop250Spider(Spider):
name = 'douban_movie_top250'
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
item = DoubanMovieItem()
movies = response.xpath('//ol[@class="grid_view"]/li')
for movie in movies:
item['ranking'] = movie.xpath('.//div[@class="pic"]/em/text()').extract()[0]
item['movie_name'] = movie.xpath('.//div[@class="hd"]/a/span[1]/text()').extract()[0]
item['score'] = movie.xpath('.//div[@class="star"]/span[@class="rating_num"]/text()').extract()[0]
item['score_num'] = movie.xpath('.//div[@class="star"]/span/text()').re(r'(\d+)人评价')[0]
yield item
.xpath() 方法返回一个类 SelectorList 的实例, 它是一个新选择器的列表。
.re() 方法用来通过正则表达式来提取数据,返回unicode字符串的列表。
.extract() 方法串行化并将匹配到的节点返回一个unicode字符串列表。
运行后报错403,豆瓣对爬虫有限制,需要修改访问的user-agent。
将 start_urls = [‘https://movie.douban.com/top250’] 改为:
class DoubanMovieTop250Spider(Spider):
name = 'douban_movie_top250'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
}
def start_requests(self):
url = 'https://movie.douban.com/top250'
yield Request(url, headers=self.headers)
start_urls URL列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
start_requests() 该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取的第一个Request。当spider启动爬取并且未指定URL时,该方法被调用。当指定了URL时,make_requests_from_url() 将被调用来创建Request对象。该方法仅仅会被Scrapy调用一次,默认实现是使用 start_urls 的url生成Request。
parse(response) 当response没有指定回调函数时,该方法是Scrapy处理下载的response的默认方法。
6.3 自动翻页 加到parse后
next_url = response.xpath('//span[@class="next"]/a/@href').extract()
if next_url:
next_url = 'https://movie.douban.com/top250' + next_url[0]
yield Request(next_url, headers=self.headers)
两种方法:
从当前页面中提取
根据URL的变化规律构造所有页面地址(适用于页面的下一页地址为JS加载)
6.4 运行
运行爬虫程序
scrapy crawl douban_movie_top250 -o douban.csv
在项目所在目录下运行爬虫,并将结果输出到.csv文件。同样支持其他序列化格式:JSON、JSON lines、XML,和多种存储方式:本地文件系统路径,ftp,Amazon S3等
报错
KeyError: 'Spider not found: douban_movie_top250'
原因是缩进问题。parse和start_requests都属于DoubanMovieTop250Spider类。
然后是settings.py出错,改为
BOT_NAME = 'douban250'
SPIDER_MODULES = ['douban250.spiders']
NEWSPIDER_MODULE = 'douban250.spiders'
然后就可以运行
6.5 输出的csv的编码问题
但是打开csv文件
有很多问题。
参考 https://blog.csdn.net/dayun555/article/details/79416447
utf-8:全球通用编码
ascii:能存储字母/数字/符号,美国专用
gbk|gb2312|gb18030:能够存储汉字
要生成经编码后的csv类型文件
cmdline.execute(['scrapy', 'crawl', '爬虫文件名称', '-o', '文件名.csv', '-s', 'FEED_EXPORT_ENCODING="gb18030"'])
例如:cmdline.execute(['scrapy', 'crawl', 'ivsky', '-o', 'img.csv', '-s', 'FEED_EXPORT_ENCODING="gb18030"'])
要生成经编码后的json类型文件
cmdline.execute(['scrapy', 'crawl', '爬虫文件名称', '-o', '文件名.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])
例如:cmdline.execute(['scrapy', 'crawl', 'ivsky', '-o', 'img.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])
修改settings,添加
FEED_EXPORT_ENCODING = "gb18030" # gbk不行
然后再运行 scrapy crawl douban_movie_top250 -o douban.csv,内容就正常了
6.6 scrapy.cmdline.execute
有这个模块可以不用手动输入。
新建auto.py
# -*- coding:utf-8 -*-
from scrapy import cmdline
# 方式一:注意execute的参数类型为一个列表
cmdline.execute('scrapy crawl spidername'.split())
# 方式二:注意execute的参数类型为一个列表
cmdline.execute(['scrapy', 'crawl', 'spidername'])
cmdline.execute(['scrapy', 'crawl', '爬虫文件名称', '-o', '文件名.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])
然后运行该文件即可。
这里
from scrapy import cmdline
cmdline.execute(['scrapy', 'crawl', 'douban_movie_top250', '-o', 'doubanauto.csv'])
6.7 扩展。settings.py
可以选择性的添加。
ROBOTSTXT_OBEY = True 是否遵守robots.txt
CONCURRENT_REQUESTS = 16 开启线程数量,默认16
AUTOTHROTTLE_START_DELAY = 3 开始下载时限速并延迟时间
AUTOTHROTTLE_MAX_DELAY = 60 高并发请求时最大延迟时间
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = ‘httpcache’
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = ‘scrapy.extensions.httpcache.FilesystemCacheStorage’
以上几个参数对本地缓存进行配置,如果开启本地缓存会优先读取本地缓存,从而加快爬取速度
USER_AGENT = ‘projectname (+http://www.yourdomain.com)’
对requests的请求头进行配置,比如可以修改为‘Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36’同样可以避免服务器返回403
6.8 扩展。pipeline.py
可以选择性的添加。
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。如果仅仅想要保存item,则不需要实现的pipeline。
item pipeline的一些典型应用有:
清理HTML数据
验证爬取的数据(检查item包含某些字段)
查重(并丢弃)
将爬取结果保存到数据库中
#去重过滤,丢弃那些已经被处理过的item。spider返回的多个item中包含有相同的id:
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item