爬虫 Scrapy 学习系列之一:Tutorial
2018-02-07 14:02
525 查看
前言
笔者打算写一系列的文章,记录自己在学习并使用 Scrapy 的点滴;作者打算使用 python 3.6 作为 Scrapy 的基础运行环境;本文为作者的原创作品,转载需注明出处;
备注:本文转载自本人的博客,伤神的博客:http://www.shangyang.me/2017/06/29/scrapy-learning-1-tutorial/
Scrapy 安装
我本地安装有两个版本的 python, 2.7 和 3.6;而正如前言所描述的那样,笔者打算使用 Python 3.6 的环境来搭建 Scrapy;1 | $ pip install Scrapy |
1 | $ pip3 install Scrap |
1 | $ pip3 install -U --timeout 1000 Scrapy |
Scrapy Tutorial
创建 tutorial 项目
使用123 | $ scrapy startproject tutorialNew Scrapy project 'tutorial', using template directory '/usr/local/lib/python2.7/site-packages/scrapy/templates/project', created in: /Users/mac/workspace/scrapy/tutorial |
123 | $ python3 -m scrapy startproject tutorialNew Scrapy project 'tutorial', using template directory '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/scrapy/templates/project', created in: /Users/mac/workspace/scrapy/tutorial |
导入 PyCharm
直接 open 项目工程 /Users/mac/workspace/scrapy/tutorial;这里需要注意的是默认的 PyCharm 使用的解释器 Interpretor 是我本地的 Python 2.7;这里需要将解释器改为 Python 3.6;下面记录下修改的步骤,点击左上角 PyCharm Community Edition,进入 Preferences
点击 Project:tutorial,然后选择 Project Interpreter,然后设置解释器的版本,如下
工程结构
通过命令构建出来的项目骨架如图所示第一个 Spider
我们来新建一个 Spider 类,名叫 quotes_spider.py,并将其放置到 tutorial/spiders 目录中12345678910111213141516171819 | import scrapyclass QuotesSpider(scrapy.Spider): name = "quotes" def start_requests(self): urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] for url in urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self, response): page = response.url.split("/")[-2] filename = 'quotes-%s.html' % page with open(filename, 'wb') as f: f.write(response.body) self.log('Saved file %s' % filename) |
name
是 Spider 的标识符,用于唯一标识该 Spider;它必须在整个项目中是全局唯一的;
start_requests()
必须定义并返回一组可以被 Spider 爬取的 Requests,Request 对象由一个 URL 和一个回调函数构成;
parse()
就是 Request 对象中的回调方法,用来解析每一个 Request 之后的 Response;所以,parse() 方法就是用来解析返回的内容,通过解析得到的 URL 同样可以创建对应的 Requests 进而继续爬取;
再来看看具体的实现,
start_request(self) 方法分别针对 http://quotes.toscrape.com/page/1/ 和 http://quotes.toscrape.com/page/2/ 创建了两个需要被爬取的
Requests 对象;并通过 yield 进行迭代返回;备注,yield 是迭代生成器,是一个 Generator;
parse(self, response) 方法既是对 Request 的反馈的内容 Response 进行解析,这里的解析的逻辑很简单,就是分别创建两个本地文件,然后将 response.body 的内容放入这两个文件当中。
如何执行
执行的过程需要使用到命令行,注意,这里需要使用到scrapy命令来执行;
12 | $ cd /Users/mac/workspace/scrapy/tutorial$ python3 -m scrapy crawl quotes |
1234567891011 | ... 2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened2016-12-16 21:24:05 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)2016-12-16 21:24:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:60232016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-1.html2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-2.html2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)... |
如何提取
通过命令行的方式提取
Scrapy 提供了命令行的方式可以对需要被爬取的内容进行高效的调试,通过使用Scrapy shell进入命令行,然后在命令行中可以快速的对要爬取的内容进行提取;
如何进入
Scrapy shell 环境
我们试着通过 Scrapy shell 来提取下 “http://quotes.toscrape.com/page/1/“ 中的数据,通过执行如下命令,进入 shell
1 | $ scrapy shell "http://quotes.toscrape.com/page/1/" |
123456789101112131415 | [ ... Scrapy log here ... ]2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)[s] Available Scrapy objects:[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s] crawler <scrapy.crawler.Crawler object at 0x7fa91d888c90>[s] item {}[s] request <GET http://quotes.toscrape.com/page/1/>[s] response <200 http://quotes.toscrape.com/page/1/>[s] settings <scrapy.settings.Settings object at 0x7fa91d888c10>[s] spider <DefaultSpider 'default' at 0x7fa91c8af990>[s] Useful shortcuts:[s] shelp() Shell help (print this help)[s] fetch(req_or_url) Fetch request (or URL) and update local objects[s] view(response) View response in a browser>>> |
通过 CSS
标准进行提取
这里主要是遵循 CSS 标准 https://www.w3.org/TR/selectors/ 来对网页的元素进行提取,
通过使用 css() 选择我们要提取的元素;下面演示一下如何提取元素 <title/>
12 | >>> response.css('title')[<Selector xpath=u'descendant-or-self::title' data=u'<title>Quotes to Scrape</title>'>] |
Selector对象中的 data 属性中的;
提取
Selector元素的文本内容,一般有两种方式用来提取,
通过使用 extract() 或者 extract_first() 方法来提取元素的内容;下面演示如何提取 #1 返回的元素 <title/> 中的文本内容 text;
12 | >>> response.css('title::text').extract_first()'Quotes to Scrape' |
Selector对象;同样也可以使用如下的方式,
12 | >>> response.css('title::text')[0].extract()'Quotes to Scrape' |
IndexError的错误;
通过 re() 方法来使用正则表达式的方式来进行提取元素的文本内容
123456 | >>> response.css('title::text').re(r'Quotes.*')['Quotes to Scrape']>>> response.css('title::text').re(r'Q\w+')['Quotes']>>> response.css('title::text').re(r'(\w+) to (\w+)')['Quotes', 'Scrape'] |
使用 XPath
除了使用 CSS 标准来提取元素意外,我们还可以使用 XPath 标准来提取元素,比如,
1234 | >>> response.xpath('//title')[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]>>> response.xpath('//title/text()').extract_first()'Quotes to Scrape' |
提取 quotes 和 authors
下面我们将来演示如何提取 http://quotes.toscrape.com 首页中的内容,先来看看首页的结构
可以看到,里面每个段落包含了一个名人的一段语录,那么我们如何来提取所有的相关信息呢?
我们从提取第一个名人的信息入手,看看如何提取第一个名人的名言信息;可以看到,第一个名人的语句是爱因斯坦的,那么我们试着来提取
名言、
作者以及相关的
tags;
123456789101112131415 | <div class="quote"> <span class="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span> <span> by <small class="author">Albert Einstein</small> <a href="/author/Albert-Einstein">(about)</a> </span> <div class="tags"> Tags: <a class="tag" href="/tag/change/page/1/">change</a> <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a> <a class="tag" href="/tag/thinking/page/1/">thinking</a> <a class="tag" href="/tag/world/page/1/">world</a> </div></div> |
首先,进入 Scrapy Shell,
1 | $ scrapy shell 'http://quotes.toscrape.com' |
<div class="quote" />元素列表
1 | >>> response.css("div.quote") |
1 | >>> quote = response.css("div.quote")[0] |
title、
author和
tags
提取
title
123 | >>> title = quote.css("span.text::text").extract_first()>>> title'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”' |
author
123 | >>> author = quote.css("small.author::text").extract_first()>>> author'Albert Einstein' |
tags,这里需要注意的是,tags 是一系列的文本,
123 | >>> tags = quote.css("div.tags a.tag::text").extract()>>> tags['change', 'deep-thoughts', 'thinking', 'world'] |
12345678 | >>> for quote in response.css("div.quote"):... text = quote.css("span.text::text").extract_first()... author = quote.css("small.author::text").extract_first()... tags = quote.css("div.tags a.tag::text").extract()... print(dict(text=text, author=author, tags=tags)){'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'} ... a few more of these, omitted for brevity |
通过
Python 程序来进行提取
本小计继续沿用提取 quotes 和 authors 小节的例子,来看看如何通过 python 程序来做相同的爬取动作;提取数据
修改该之前的 quotes_spider.py 内容,如下,
12345678910111213141516 | import scrapyclass QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('small.author::text').extract_first(), 'tags': quote.css('div.tags a.tag::text').extract(), } |
quotes的爬虫,
1 | $ scrapy crawl quotes |
1234 | 2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"} |
quotes一条一条的返回了爬取的信息;
保存数据
最简单保存方式被爬取的数据是通过使用 Feed exports,通过使用如下的命令,
使用 JSON 格式
1 | $ scrapy crawl quotes -o quotes.json |
quotes.json,该文件中包含了所有被爬取的数据;不过由于历史的原因,Scrapy 是往一个文件中追加被爬取的信息,而不是覆盖更新,所以如果你执行上述命令两次,将会得到一个损坏了的 json 文件;
使用 JSON Lines 格式
1 | $ scrapy crawl quotes -o quotes.jl |
.jl;
补充,JSON Lines 是另一种 JSON 格式的定义,基本设计是每行是一个有效的 JSON Value;比如它的格式比 CSV 格式更友好,
12345 | ["Name", "Session", "Score", "Completed"]["Gilbert", "2013", 24, true]["Alexa", "2013", 29, true]["May", "2012B", 14, false]["Deloise", "2012A", 19, true] |
1234 | {"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}{"name": "May", "wins": []}{"name": "Deloise", "wins": [["three of a kind", "5♣"]]} |
注意的是,使用 JSON lines 的方式,Scrapy 同样的是以
追加的方式添加内容,只是因为 JSON Lines 逐行的方式添加被爬取的数据,所以以追加的方式并不会想使用 JSON 格式那样导致文件格式错误;
如果是一个小型的项目,使用 JSON Lines 的方式就足够了;但是,如果你面临的是一个更复杂的项目,而且有更复杂的数据需要爬取,那么你就可以使用 Item Pipeline;一个 demo
Pipelines 已经帮你创建好了,
tutorial/pipelines.py;
提取下一页(提取链接信息)
如何提取章节详细的描述了如何爬取页面的信息,那么,如何爬取该网站的所有信息呢?那么就必须爬取相关的链接信息;那么我们依然以 http://quotes.toscrape.com 为例,来看看我们该如何爬取链接信息,我们可以看到,下一页的链接 HTML 元素,
12345 | <ul class="pager"> <li class="next"> <a href="/page/2/">Next <span aria-hidden="true">→</span></a> </li></ul> |
12 | >>> response.css('li.next a').extract_first()'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>' |
anchor元素,但是我们想要得到的是其
href属性;Scrapy 支持 CSS 扩展的方式,因此我们可以直接爬取其属性值,
12 | >>> response.css('li.next a::attr(href)').extract_first()'/page/2/' |
使用 scrapy.Request
1234567891011121314151617181920 | import scrapyclass QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('small.author::text').extract_first(), 'tags': quote.css('div.tags a.tag::text').extract(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: next_page = response.urljoin(next_page) yield scrapy.Request(next_page, callback=self.parse) |
爬虫队列中,等待下一次的爬取;由此,你就可以动态的去爬取所有相关页面的信息了;
基于此,你就可以建立起非常复杂的爬虫了,同样,可以根据不同链接的类型,构建不同的 Parser,那么就可以对不同类型的返回页面进行分别处理;
使用 response.follow
不同于使用 scrapy Request,需要通过相对路径构造出绝对路径,response.follow 可以直接使用相对路径,因此就不需要调用 urljoin 方法了;注意,response.follow 直接返回一个Request 实例,可以直接通过 yield 进行返回;所以,上述代码可以简化为
12345678910111213141516171819 | import scrapyclass QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('span small::text').extract_first(), 'tags': quote.css('div.tags a.tag::text').extract(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: yield response.follow(next_page, callback=self.parse) |
<a>元素的时候,会直接使用它们的 href 属性;所以上述代码还可以简化为,
123 | next_page = response.css('li.next a').extract_first()if next_page is not None: yield response.follow(next_page, callback=self.parse) |
定义更多的 Parser
1234567891011121314151617181920212223242526 | import scrapyclass AuthorSpider(scrapy.Spider): name = 'author' start_urls = ['http://quotes.toscrape.com/'] def parse(self, response): # follow links to author pages for href in response.css('.author + a::attr(href)'): yield response.follow(href, self.parse_author) # follow pagination links for href in response.css('li.next a::attr(href)'): yield response.follow(href, self.parse) def parse_author(self, response): def extract_with_css(query): return response.css(query).extract_first().strip() yield { 'name': extract_with_css('h3.author-title::text'), 'birthdate': extract_with_css('.author-born-date::text'), 'bio': extract_with_css('.author-description::text'), } |
进入 parse(),从当前的页面中爬取得到所有相关的 author href 属性值既是一个链接,然后针对该链接,通过 response.follow 创建一个新的 Request 继续进行爬取,通过回调 parse_author() 方法对爬取的内容进行进一步的解析,这里就是对爬取到的 Author 的信息进行提取;
当 #1 有关当前页面所有的 Author 信息都已经爬取成功以后,便开始对下一页进行爬取;
从这个例子中,我们需要注意的是,当爬取当前页面的时候,我们依然可以通过创建子的 Requests 对子链接进行爬取直到所有有关当前页面的信息都已经被爬取完毕以后,方可进入下一个页面继续进行爬取;
另外,需要注意的是,在爬取整个网站信息的时候,必然会有多个相同 Author 的名言,那么势必要爬取到许多的重复的 Author 的信息;这无疑是增加了爬取的压力同时也需要处理大量的冗余数据,基于此,Scrapy 默认实现了对
重复的已经爬取过的链接在下次爬取的时候
自动过滤掉了;不过,你也可以通过
DUPEFILTER_CLASS来进行设置是否启用该默认行为;
使用 Spider 参数
你可以通过 commond line 的方式为你的 Spider 提供参数,1 | $ scrapy crawl quotes -o quotes-humor.json -a tag=humor |
12345678910111213141516171819202122 | import scrapyclass QuotesSpider(scrapy.Spider): name = "quotes" def start_requests(self): url = 'http://quotes.toscrape.com/' tag = getattr(self, 'tag', None) if tag is not None: url = url + 'tag/' + tag yield scrapy.Request(url, self.parse) def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('small.author::text').extract_first(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: yield response.follow(next_page, self.parse) |
getattr(self, 'tag', None)便可以获取从 common line 中传入的 tag 参数,并构造出需要爬取的 URL 链接 http://quotes.toscrape.com/tag/humor
Reference
Scrapy 爬虫框架:http://python.jobbole.com/86405/Installation Guide: https://doc.scrapy.org/en/latest/intro/install.html
explain how virtualenv used: https://stackoverflow.com/questions/41151141/how-to-get-scrapy-to-use-python-3-when-both-python-versions-are-installed
tutorial guide: https://doc.scrapy.org/en/latest/intro/tutorial.html
Scrapy Clusters: http://scrapy-cluster.readthedocs.io/en/latest/
Scrapy Deployment: https://scrapyd.readthedocs.io/en/latest/deploy.html
Scrapy 0.24 文档:http://scrapy-chs.readthedocs.io/zh_CN/0.24/index.html
相关文章推荐
- 爬虫 Scrapy 学习系列之一:Tutorial
- 爬虫 Scrapy 学习系列之一:Tutorial
- scrapy爬虫学习系列五:图片的抓取和下载
- scrapy爬虫学习系列二:scrapy简单爬虫样例学习
- [置顶] Scrapy学习系列之拉勾网爬虫实践
- scrapy爬虫学习系列四:portia的学习入门
- scrapy爬虫学习系列三:scrapy部署到scrapyhub上
- [置顶] Scrapy学习系列之天眼查爬虫实践
- scrapy爬虫学习系列一:scrapy爬虫环境的准备
- scrapy爬虫学习系列四:portia的学习入门
- 【Python学习系列五】Python网络爬虫框架Scrapy环境搭建
- Python爬虫学习系列教程
- python 爬虫 学习笔记(一)Scrapy框架入门
- Scrapy爬虫框架学习之MySQL数据库的存储
- Python 爬虫学习系列教程----目录
- 爬虫学习笔记-Scrapy散记
- Scrapy学习系列之Selenium + Chrome + Xpath实践记录
- 爬虫学习之Scrapy构建
- 爬虫scrapy之学习路线
- Scrapy爬虫学习,及实践项目。