初窥Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了 网络抓取 所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services 或者通用的网络爬虫。
Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下
Scrapy主要包括了以下组件:
- 引擎(Scrapy) :用来处理整个系统的数据流, 触发事务(框架核心)
- 调度器(Scheduler) :用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader) :用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders) :爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline) :负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
- 下载器中间件(Downloader Middlewares) :位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
- 爬虫中间件(Spider Middlewares) :介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
- 调度中间件(Scheduler Middewares) :介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
- 引擎从调度器中取出一个链接(URL)用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器
- 下载器把资源下载下来,并封装成应答包(Response)
- 爬虫解析Response
- 解析出实体(Item),则交给实体管道进行进一步的处理
- 解析出的是链接(URL),则把URL交给调度器等待抓取
搭建Scrapy环境
新建虚拟环境
python3 -m venv /data/code/python/venv/venv_Scrapy
升级pip
/data/code/python/venv/venv_Scrapy/bin/pip3 install --upgrade pip
pip安装Scrapy
/data/code/python/venv/venv_Scrapy/bin/pip3 install Scrapy
创建项目
新建Scrapy项目tutorial
#进入虚拟环境
cd /data/code/python/venv/venv_Scrapy/
#新建项目tutorial
./bin/python3 ./bin/scrapy startproject tutorial
tutorial项目结构
tutorial/
├── tutorial
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ └── __init__.py
└── scrapy.cfg
其中:
- scrapy.cfg : 项目配置文件。
- settings.py : 该文件定义了一些设置,如用户代理,爬取延时等,详细说明。
- items.py : 该文件定义了待抓取域的模型,详细说明。
- pipelines.py : 该文件定义了数据的存储方式(处理要抓取的域),可以是文件,数据库或者其他。详细说明
- middlewares.py: 爬虫中间件,该文件可定义随机切换ip或者用户代理的函数。详细说明
genspider命令新建爬虫
genspider语法
- 语法:
scrapy genspider [-t template] <name> <domain>
新建百度爬虫
#进入虚拟环境
cd /data/code/python/venv/venv_Scrapy/
#新建百度爬虫
../bin/python3 ../bin/scrapy genspider -t basic baidu_search www.baidu.com
baidu_search.py
# -*- coding: utf-8 -*-
import scrapy
class BaiduSearchSpider(scrapy.Spider):
name = 'baidu_search'
allowed_domains = ['www.baidu.com']
start_urls = ['http://www.baidu.com/']
def parse(self, response):
pass
scrapy.Spider
class scrapy.spiders.Spider
Spider是最简单的spider。每个其他的spider必须继承自该类(包括Scrapy自带的其他spider以及您自己编写的spider)。 Spider并没有提供什么特殊的功能。 其仅仅提供了 start_requests()
的默认实现,读取并请求spider属性中的 start_urls
, 并根据返回的结果(resulting responses)
调用spider的 parse
方法。
-
name
定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。 不过您可以生成多个相同的spider实例(instance),这没有任何限制。 name是spider最重要的属性,而且是必须的。
如果该spider爬取单个网站(single domain),一个常见的做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite 。 -
allowed_domains
可选。包含了spider允许爬取的域名(domain)列表(list)。 当 OffsiteMiddleware 启用时, 域名不在列表中的URL不会被跟进。 -
start_urls
URL列表。当没有指定特定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
crawl开始爬虫
- 语法:
scrapy crawl <spider>
#进入虚拟环境
cd /data/code/python/venv/venv_Scrapy/
#crawl开始爬虫
../bin/python3 ../bin/scrapy crawl baidu_search
Forbidden by robots.txt
2018-12-05 13:15:04 [scrapy.utils.log] INFO: Scrapy 1.5.1 started (bot: tutorial)
2018-12-05 13:15:04 [scrapy.utils.log] INFO: Versions: lxml 4.2.5.0, libxml2 2.9.8, cssselect 1.0.3, parsel 1.5.1, w3lib 1.19.0, Twisted 18.9.0, Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24) - [Clang 6.0 (clang-600.0.57)], pyOpenSSL 18.0.0 (OpenSSL 1.1.0j 20 Nov 2018), cryptography 2.4.2, Platform Darwin-16.7.0-x86_64-i386-64bit
2018-12-05 13:15:04 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'tutorial', 'DUPEFILTER_CLASS': 'scrapy_splash.SplashAwareDupeFilter', 'HTTPCACHE_STORAGE': 'scrapy_splash.SplashAwareFSCacheStorage', 'NEWSPIDER_MODULE': 'tutorial.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MODULES': ['tutorial.spiders']}
2018-12-05 13:15:04 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.memusage.MemoryUsage',
'scrapy.extensions.logstats.LogStats']
2018-12-05 13:15:04 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy_splash.SplashCookiesMiddleware',
'scrapy_splash.SplashMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2018-12-05 13:15:04 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy_splash.SplashDeduplicateArgsMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2018-12-05 13:15:04 [scrapy.middleware] INFO: Enabled item pipelines:
['tutorial.pipelines.QQVideoCommentPipeline']
2018-12-05 13:15:04 [scrapy.core.engine] INFO: Spider opened
2018-12-05 13:15:04 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-12-05 13:15:04 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-12-05 13:15:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.baidu.com/robots.txt> (referer: None)
2018-12-05 13:15:04 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET http://www.baidu.com/>
2018-12-05 13:15:05 [scrapy.core.engine] INFO: Closing spider (finished)
2018-12-05 13:15:05 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
'downloader/request_bytes': 281,
'downloader/request_count': 1,
'downloader/request_method_count/GET': 1,
'downloader/response_bytes': 677,
'downloader/response_count': 1,
'downloader/response_status_count/200': 1,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2018, 12, 5, 5, 15, 5, 55729),
'log_count/DEBUG': 3,
'log_count/INFO': 7,
'memusage/max': 53755904,
'memusage/startup': 53755904,
'response_received_count': 1,
'scheduler/dequeued': 1,
'scheduler/dequeued/memory': 1,
'scheduler/enqueued': 1,
'scheduler/enqueued/memory': 1,
'start_time': datetime.datetime(2018, 12, 5, 5, 15, 4, 737516)}
2018-12-05 13:15:05 [scrapy.core.engine] INFO: Spider closed (finished)
首次开启爬虫,可能会遇到Forbidden by robots.txt
robots协议
Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
访问一下 https://www.baidu.com/robots.txt
最后我们可以看见
.....
User-agent: *
Disallow: /
简单来说,就是百度禁止一切爬虫。当然,尊不遵守君子协定就看你个人了。我们这里先到 settings.py 禁止掉
ROBOTSTXT_OBEY
编辑 settings.py 修改以下设置
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
爬取百度首页
这里我们修改 baidu_search.py 打印百度首页
# -*- coding: utf-8 -*-
import scrapy
class BaiduSearchSpider(scrapy.Spider):
name = 'baidu_search_'
allowed_domains = ['www.baidu.com']
start_urls = ['http://www.baidu.com/']
def parse(self, response):
print(response.text)
再次运行,就可以看到我们已经拿到了百度的首页了
../bin/python3 ../bin/scrapy crawl baidu_search