python3爬虫学习系列08 - scrapy(一)

文章目录

  • 1. 安装Scrapy
  • 2. 创建一个项目
  • 3. 如何运行爬虫
  • 4. 运行这个爬虫的时候发生了什么?
  • 5. 提取数据
    • 5.1 CSS 选择器提取数据
    • 5.2 XPath 提取数据
    • 5.3 提取指定数据
    • 5.4 集成到我们的爬虫中
  • 6. 保存提取的数据
  • 7. 参考文献

之前的博客:
爬虫学习系列02-常见的下载和抽取网页的方法
爬虫学习系列03-下载缓存
爬虫学习系列04 - 并发下载
爬虫学习系列05 - 获取动态内容
python3爬虫学习系列06 -表单交互
python3爬虫学习系列07 - 处理验证码

scrapy是一个流行的网络爬虫框架,它拥有很多简化网站抓取的高级函数。本节中还会简单介绍Portia,这是一个基于Scrapy的应用,允许用户通过点击界面抓取网站。

1. 安装Scrapy

因为scrapy需要许多依赖,所以不一定直接使用pip install scrapy就能直接装上,最好是在独立的虚拟环境中安装scrapy,避免与系统中现有的包发生冲突。若出现问题,查看scrapy官方文档。
python中有一个virtualenv包可以创建虚拟环境,如果是在Linux 或 OS X系统中还可以使用virtualenvwrapper结合virtualenv使用。

如果系统是windows 推荐使用 Anaconda 或 Miniconda。如果已经安装了 Anaconda 或 Miniconda ,直接使用以下语句安装scrapy,能够避免许多安装过程中的问题。

conda install -c conda-forge scrapy

Scrapy使用纯python编写的,在使用它的时候通常需要用到一些其它的python包,如:

  • lxml, 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
  • parsel,是scrapy内置的选择器包含re、css、xpath选择器,依赖lxml,可以方便地在网页内容中选择到需要的部分。
  • w3lib,用于处理URL和网页编码的多用途帮助程序。
  • twisted, 异步网络框架。
  • cryptography and pyOpenSSL,,处理各种网络级安全需求。

2. 创建一个项目

如果scrapy安装成功,那么就可以在终端里执行scrapy命令了

scrapy startproject <project name>
# 例如,创建一个tutorial项目
scrapy startproject tutorial

使用上面的命令生成的文件结构。

tutorial/
    scrapy.cfg            # 部署配置文件
    tutorial/             # 项目的Python模块,你将从这里导入你的代码
        __init__.py
        items.py          # 项目模型文件。定义待抓取的域名的模型,抓取页面中的哪些数据。
        middlewares.py    # 项目中间件文件。
        pipelines.py      # 项目管道文件。实现数据的清洗,储存,验证。
       settings.py       # 项目全局设置文件,例如用户代理、爬取延时等。
       spiders/          # 你的爬虫代码将会放在这个目录下。
            __init__.py

一个简单的爬虫代码。

# tutorial/spiders/quotes_spider.py
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
    	# 起始的url
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
        	# 请求成功之后,调用回调函数,此处指明了调用self.parse函数来处理请求的响应
            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)

从下面的代码可知,我们定义了一个scrapy.Spider类的子类。在我们定义的爬虫类中,有许多的属性和方法

name: 爬虫的名字。对于一个项目而言,两个不同的爬虫不能具有相同的名字,名字是唯一的,用来识别是哪个爬虫。

start_requests(): 返回的是一个Requests的可迭代对象(可以返回requests的列表或生成器对象),爬虫将会从这个或这些requests去爬取。后续的requests将依次由这些初始requests生成。

parse(): 这个方法用来处理每一个request(请求)的response(响应),response(响应)其实是TextResponse的一个实例,它保存页面内容并具有处理它的一些方法。parse() 方法通常用来解析请求的响应,抽取需要的数据为一个字典,发现新的url,并且为它们创建新的requests(请求)。

3. 如何运行爬虫

在项目的根目录下,在这儿也就是在 tutorial/ 目录下,运行如下命令即可。还记得quotes是我们写在 quotes_spider.py 中的爬虫的名字,在这里就爬上用场了。

scrapy crawl quotes

在终端的输出大致是这样的。

... (omitted for brevity)
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened
2016-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:6023
2016-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.html
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-2.html
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)

此时,项目目录中会多出两个html文件,quotes-1.html 和 quotes-2.html,这就是经过我们的爬虫代码下载和解析之后的html。

4. 运行这个爬虫的时候发生了什么?

  • 调用了quotes_spider.py中的start_requests()方法,该方法返回了scrapy.Request对象。
  • 接收到每个url产生的请求对应的resopnse响应之后,将response响应作为一个参数传递给回调函数(这里是parse()),调用该回调函数来处理response对象。

当然,并不是一定要直接使用 start_requests() 来,我们可以直接定义一个名为 start_urls (list类型) 的类属性来代替 start_requests()方法,scrapy 会调用 start_requests() 方法使用 start_urls 得到初始url的请求对象。

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)

尽管我们并没有显式地告诉 scrapy 使用 parse() 方法来解析请求的响应,但是因为 parse() 是scrapy默认的回调函数,没有明确指明回调函数的情况下,仍然使用 parse() 作为回调函数。

5. 提取数据

5.1 CSS 选择器提取数据

学习如何使用Scrapy提取数据的最佳方法是使用Scrapy shell尝试选择器。

scrapy shell 'http://quotes.toscrape.com/page/1/'

当从命令行运行scrapy shell时,请记住始终将URL括在引号中,否则将不起作用。
注意,在windows系统中必须要使用双引号。

scrapy shell "http://quotes.toscrape.com/page/1/"

终端中的输出大致如下:

[ ... 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
>>>

在使用 scrapy shell 的时候,我们可以使用response对象的css选择器来选择html中的某些元素,例如,获得’< title >'元素

>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='Quotes to Scrape'>]

response.css(‘title’)的运行结果是一个 SelectorList 对象,它是由一个或多个包含XML / HTML元素的Selector对象构成的list,并允许你进行更进一步地查询以细化选择的内容或提取数据。

要从上面的标题中提取文本,您可以执行以下操作:

>>> response.css('title::text').getall()
['Quotes to Scrape']

在这里有两点需要注意

  1. 我们在css选择器中写了 ::text ,这意味着我们我们只想要选择到< title > 标签中的文本 (text) 元素。
  2. 如果不指明 ::text ,那么就会选择到 < title > 标签的所有元素,也包括了< title >标签。
>>> response.css('title').getall()
['Quotes to Scrape']

.getall() 的返回结果是一个list:因为一个选择器可能会返回多个符合的结果,使用 .getall() 将会提取 response 中的所有结果。如果你只需要提取其中的第一个结果,那么:

>>> response.css('title::text').get()
'Quotes to Scrape'

这样做也可以获得第一个结果,

>>> response.css('title::text')[0].get()
'Quotes to Scrape'

由于css选择器返回的结果是一个 SelectList ,那么当满足选择条件的结果不存在时,直接在 SelectorList 对象调用 get() 方法可以避免出现 IndexError 异常,并且返回 None。因此,建议使用前一种方式。

需要注意的是,对于大多数提取数据的代码而言,应当有较高灵活性,也就是说,如果即使没有抓取到任何数据或则是有一部分数据抓取失败,我们仍然能够得到一些数据。

除了 .getall() 和 .get() 方法之外,我们还可以使用 re() 和正则表达式来提取数据。

>>> 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']

In order to find the proper CSS selectors to use, you might find useful opening the response page from the shell in your web browser using view(response). You can use your browser’s developer tools to inspect the HTML and come up with a selector (see Using your browser’s Developer Tools for scraping).

为了找到合适的 CSS 选择器,一个有效的方法是,在浏览器中使用 view 打开响应页面。

  • 在浏览器中找到想要提取的标签。
  • 右键 > copy > copy selctor, 然后粘贴到代码中即可。

5.2 XPath 提取数据

除了CSS以外,Scrapy选择器还支持XPath表达式。

>>> response.xpath('//title')
[<Selector xpath='//title' data='Quotes to Scrape'>]
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'

XPath 表达式非常强大,它是Scrapy选择器的基础。事实上,CSS选择器是通过转换成XPath表达式来实现提取元素。虽然XPath不像CSS选择器那样流行,但XPath表达式提供了更多功能,因为除了导航结构外,它还可以查看内容。

例如,使用XPath,您可以选择以下内容:选择包含文本“下一页”的链接。

<link> 下一页link>

这使XPath非常适合抓取任务,即使你已经知道如何构造CSS选择器,我们仍然鼓励你学习XPath,它也会使抓取更容易。

在Scrapy中关于XPath的详细使用请移步官方文档 Scrapy中使用XPath

5.3 提取指定数据

示例html代码如下:

<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 Einsteinsmall>
        <a href="/author/Albert-Einstein">(about)a>
    span>
    <div class="tags">
        Tags:
        <a class="tag" href="/tag/change/page/1/">changea>
        <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughtsa>
        <a class="tag" href="/tag/thinking/page/1/">thinkinga>
        <a class="tag" href="/tag/world/page/1/">worlda>
    div>
div>

我们打开 scrapy shell 来看看如何提取到需要的数据:

$ scrapy shell 'http://quotes.toscrape.com'
# 得到一个选择器的list
>>> response.css("div.quote")
# 对于每一个返回的选择器,我都可以做进一步的操作,现将某一选择器赋值给一个变量。
>>> quote = response.css("div.quote")[0]
# 从变量quote中提取出text,author和tags
>>> text = quote.css("span.text::text").get()
>>> text
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
>>> author = quote.css("small.author::text").get()
>>> author
'Albert Einstein'
# tags是字符串的list,使用.getall()可以得到全部的tags的文本内容
>>> tags = quote.css("div.tags a.tag::text").getall()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']

# 使用for循环迭代方式提取html中的某些数据,并存入一个字典中。
>>> for quote in response.css("div.quote"):
...     text = quote.css("span.text::text").get()
...     author = quote.css("small.author::text").get()
...     tags = quote.css("div.tags a.tag::text").getall()
...     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
>>>

5.4 集成到我们的爬虫中

上面介绍了 CSS 选择器和 XPath 的简单使用方法,因为我们之前的爬虫代码只是保存了html内容而已,并没有从html提取任何数据,现在将提取数据功能集成到我们的爬虫代码中,实现从html中提取数据的功能。

通常情况下,Scrapy爬虫会生成许多包含从页面提取的数据的字典。 为此,我们在回调中使用 yield 关键字,如下所示:

# tutorial/spiders/quotes_spider.py
import scrapy


class 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').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

运行上面的爬虫在日志中会得到如下的输出。

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.”"}

6. 保存提取的数据

存储提取的数据的最简单方式就是 Feed Export。

scrapy crawl quotes -o quotes.json

运行上面的命令会生成一个名为 quotes.json 的 json 文件,这个文件中存储了序列化之后的爬取数据。

需要注意的是,在第二次使用上面的命令时,并不会覆盖已经存在的 quotes.json 文件中的内容,而是追加到文件中。这是某些历史原因造成。因此,在使用的时候一定要注意,否则得到的 json 文件中的数据是有误的。

我们不是只能将爬取的数据存为 .json 格式,我们还可以存为 .jl 格式(JSON Line)。

scrapy crawl quotes -o quotes.jl

.jl 格式有它自己的用处,这个格式类似于流(stream),它能够很方便的添加新的记录,而且当运行两次这个命令的时候,并不会出现像 JSON 格式那样的问题。每一条记录都是单独的一行,在处理大文件的时候,无需将整个文件都放入内存中,这样就可以很简单地实现读取大文件,而 JOSN 则无法做到这一点,因为 JSON 格式的文件无法部分读取(会出现json格式有误的问题,而造成文件无法读取)。.jl 文件通常结合 JQ 工具使用。

这个 tutorial 项目只是一个很小、很简单的 scrapy 项目,如果爬虫项目更为复杂的话,需要使用 item pipeline 。在项目中的 tutorial/pipelines.py 文件中定义了 item pipeline 的配置。如果您只想存储已爬取的 items,则不需要实现任何 item pipeline。

7. 参考文献

[1]《用python写web爬虫(web scraping with python)》
[2] scrapy 官方文档
[3] 爬虫框架Scrapy的安装与基本使用 - 简书

你可能感兴趣的:(python3,爬虫)