发布者: Jeremy Peng

1 简介

有时候我们需要从各种网页或网站中,获取我们需要的信息或者内容,通常我们可以通过urllib 获取网页的html 代码,然后利用BeautifulSouplxml 等xml parser解析出我们需要的内容。

但是这种解析方法和过程比较繁琐,代码最后也会有很多的各种html tag判断分支,构成的爬虫程序通常比较复杂,难以维护和复用。

这时候我们需要一个简单易用的“爬虫框架” 来为我们完成繁琐的数据获取过程,使得我们能更专注于数据处理上。

Scrapy comes to help!

Scrapy 是基于python 一个开源的“网络爬虫” 框架,功能上要比基于 BeautifulSoup 或 lxml 的爬虫强大, 用官方的一句话:

In other words, comparing BeautifulSoup (or lxml) to Scrapy is like comparing jinja2 to Django.

Scrapy 现在暂时只支持Python2.7版本(3.0 不支持), 2.6版本的python 只能使用scrapy 0.20.

2 安装

scrapy的安装极其简单,只需要执行:

pip install scrapy

经过漫长的等待, scrapy终于安装完成。(如果之前没有安装过pip, 请移步

然后再终端里执行“scrapy”,即能看到scrapy相关信息,要是提示找不到命令,则需要将python安装目录下的scripts加入到环境变量中。

如运行时发生import win32api 的error, 则需要安装pywin32 3 使用简介 === 使用scrapy 通常需要以下四个步骤:

  1. 新建工程(Project): 新建新的爬虫工程
  2. 定义目标 (Items): 确定需要从网页中获取的内容
  3. 生成爬虫(Spider): 定义爬取的url及爬取规则
  4. 保存内容(Pipeline): 保存处理爬取到的内容

3.1 新建工程

首先cd 到需要新建工程的目录下,然后执行:

scrapy startproject scrapy_test

scrapy_test 是工程的名称,随后将创建一个scrapy_test目录,其目录格式如下:

各个目录及文件的作用如下所示:

scrapy.cfg:工程的配置文件 

scrapy_test:工程的Python文件目录 

scrapy_test/items.py:项目的items文件, 我们需要在此文件中定义需要从网页中获取的内容,对应上面说的步骤2 

scrapy_test/pipelines.py:项目的pipelines文件, 用于保存处理爬取到的内容, 对应于步骤4 

scrapy_test/settings.py:项目的设置文件 

scrapy_test/spiders/:存储爬虫的目录, 对应步骤3

3.2 定义目标

这个步骤可以理解为确定我们需要从网页爬取的内容, 是需要爬取链接? 标题? 还是其他内容?

在scrapy 我们利用 “Item” 来代表保存爬取到的一条信息, 它可以包含多个内容, 具体的内容用”Field” 表示:

举个例子, 假定我们需要抓取 从 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/

抓取每本书的标题, 链接和简单的描述, 那么我们在 scrapy_test/items.py 添加如下目标:

from scrapy.item import Item, Field 
 
class DmozItem(Item): 
    title = Field() 
    link = Field() 
    desc = Field()

可以把Item理解成更为安全的字典 , 而Field 则是字典里的内容.

定义完以上目标后, 我们可以将DmozItem 当成一般的字典操作:

>>> item = DmozItem()
>>> item['title'] = 'Example title'
>>> item['title']
 
'Example title'

也很容易想到, 具体item里的内容,将由我们的Spider 获取并填充.

3.3 生成爬虫

前面的准备工作完成, 终于可以开始制作我们勤劳又能干的spider了

3.3.1 第一个Spider

Spider是用户定义的类, 用来获取特定域或者网站中指定的内容. 在爬虫里,我们定义了爬取的初始化的URL列表, 如何跟踪爬取到的新的link 及如何解析网页的内容填充我们前面定义的item.

Spider 都是从scrapy.Spider 派生, 并必须定义以下3个属性:

name: 用于标识每个spider, 同一工程内的spider的名字必须唯一.

start_urls: 定义了spider开始爬取的URL列表,  Spider 将从这些定义的网页开始爬取

parse(): 爬虫的主要方法, 这个方法负责解析返回的URL数据, 匹配抓取到的数据填充Item, 并能跟踪更多的URL进行深入爬取.

我们还是来结合前面Item 定义的网页进行说明, 我们定义如下Spider:

import scrapy
 
class DmozSpider(scrapy.Spider):
    name = "dmoz"
    allowed_domains = ["dmoz.org"]
    start_urls = [
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
    ]
    def parse(self, response):
        filename = response.url.split("/")[-2]
        with open(filename, 'wb') as f:
            f.write(response.body)

我们将这个Spider命名为dmoz_spider.py, 并保存在scrapy_test/spiders 目录下.

这个Spider不具备解析数据功能, 我们只是用它来说明spider的结构, 并以此说明如何运行我们的Spider.

3.3.2 运行Spider

首先我们将当前目录变更为我们建立scrapy_test工程的目录(与scrapy.cfg 文件同级), 并运行: scrapy crawl dmoz

该命令将运行名为”dmoz” 的spider, 这个名字正是我们在前面DmozSpider定义的name, 然后我们将得到类似如下输出:

2014-11-05 17:28:37+0800 [scrapy] INFO: Scrapy 0.24.4 started (bot: scrapy_test)
2014-11-05 17:28:37+0800 [scrapy] INFO: Optional features available: ssl, http11
2014-11-05 17:28:37+0800 [scrapy] INFO: Overridden settings: {'NEWSPIDER_MODULE': ..
...
2014-11-05 17:28:37+0800 [scrapy] INFO: Enabled spider middlewares: ..
2014-11-05 17:28:37+0800 [scrapy] INFO: Enabled item pipelines:
2014-11-05 17:28:37+0800 [dmoz] INFO: Spider opened
2014-11-05 17:28:37+0800 [dmoz] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2014-11-05 17:28:37+0800 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2014-11-05 17:28:37+0800 [scrapy] DEBUG: Web service listening on 127.0.0.1:6080
2014-11-05 17:28:39+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-11-05 17:28:39+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2014-11-05 17:28:39+0800 [dmoz] INFO: Closing spider (finished)
2014-11-05 17:28:39+0800 [dmoz] INFO: Dumping Scrapy stats:
 ......
2014-11-05 17:28:39+0800 [dmoz] INFO: Spider closed (finished)

留意包含[dmoz] 的那些行, 这些log是我们的spider在爬取过程中产生的, ( 在我们的Spider里我们可以利用scrapy.log.msg 打印需要的信息)

这个Spider 的功能很简单, 运行完后, 我们发现在scrapy_test的顶层工程目录下生成了两个文件”Resources” 和 “Books” , 他们各自保存了start_urls 定义网页的html代码

Scrapy 为start_urls里定义的每个URL生成一个scrapy.Request对象, 然后指定parse方法为它们的回调函数;

在这些scrapy.Request对象发送并执行后, 将会返回scrapy.http.Response对象并通过parse方法传递给我们的spider.

要使上面的Spider 具备爬取指定内容的功能, 这就需要选择器Selector 出场了.

3.3.3 Selector选择器

Scrapy 使用的selector机制是基于 XPath 或者 CSS 表达式实现的. 其中XPath更易使用,因此本文仅介绍利用XPath进行数据提取.

如果你想了解更多selectors和其他机制你可以查阅资料 http://doc.scrapy.org/en/0.24/topics/selectors.html

下面列出了最有用的XPath表达式:

表达式 描述
nodename 选取此节点的所有子节点。
.. 选取当前节点的父节点。
. 选取当前节点。
@ 选取属性。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。

XPath example:

  • /html/head/title: 选择HTML文档<head>元素下面的 标签。
  • /html/head/title/text(): 选择前面提到的 元素下面的文本内容
  • //td: 选择所有 <td> 元素
  • //div[@class=”mine”]: 选择所有包含 class=”mine” 属性的div 标签元素

关于XPATH的介绍可以参考教程http://www.w3schools.com/XPath/default.asp

Scrapy提供了名为Selector类来对得到的response对象进行内容提取. 你可以将selector看成是xml 文档结构的某个节点, 第一个节点就是根节点.

Selector 提供以下4个基本方法:

  1. xpath(): 返回一系列selectors, 每一个selector代表了用xpath表达式选择的节点
  2. css(): 返回一系列selectors, 每一个selector代表了用css表达式选择的节点
  3. extract(): 返回selector提取到的数据的unicode字符串
  4. re(): 返回selector经正则表达式提取到的unicode字符串

为了测试selector, 我们使用scrapy 内建的Scrapy Shell 进行测试 (需要IPython 支持)

执行:

scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"

我们将得到:

运行完这个命令后, 我们将得到获取自指定网页的response对象, 如果我们输入response.body将能看到返回的html代码.

我们可以通过”response.selector”获得一个selector对象用于从而进行xpath或者css的查询.

为了方便输入, reponse.xpth和response.css直接映射到response.selector.xpath() 和 response.selector.css()

我们可以尝试以下xpath命令:

3.3.4 提取数据

到此我们可以利用selector使我们的spider具有数据提取的功能了

按之前举的例子, 我们需要抓取 从 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/

抓取每本书的标题, 链接和简单的描述, 利用Chrome Developer Tools (F12) 或者其他的Xpath插件, 我们可以查询到这三项内容的html 规律是:

标题: 在以具有”class=directory-url”属性的<ul>为父节点的, <li>标签下,具有”class=listinglink”属性的<a>标签的内容

链接: 在以具有”class=directory-url”属性的<ul>为父节点的, <li>标签下,具有”class=listinglink”属性的<a>标签的href链接

描述: 在以具有”class=directory-url”属性的<ul>为父节点的, <li>标签的内容

因为这三项内容都是在<li>标签下, 因此提取公共部分:

sel = response.xpath(“//ul[@class=’directory-url’]/li”)

将上述规律转换成xpath 可得:

标题: title = sel.xpath(“a/text()”).extract()

链接: link = sel.xpath(“a/@href”).extract()

描述: desc = sel.xpath(“text()”).extract()

因此修改3.3.1的代码为:

import scrapy
from scrapy import log
class DmozSpider(scrapy.Spider):
    name = "dmoz"
    allowed_domains = ["dmoz.org"]
    start_urls = [
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
    ]
    def parse(self, response):
        for sel in response.xpath("//ul[@class='directory-url']/li"):
            title = sel.xpath("a/text()").extract()
            link = sel.xpath("a/@href").extract()
            desc = sel.xpath("text()").extract()
            print "title:" , title
            print "link:" , link
            print "desc:" , desc

用 scrapy crawl dmoz 运行, 我们将能得到爬取到的信息.

3.3.5 使用 DmozItem

前面我们定义了Item, 现在我们可以在spider里填充我们的item了:

import scrapy
from scrapy import log
from scrapy_test.items import DmozItem
class DmozSpider(scrapy.Spider):
    name = "dmoz"
    allowed_domains = ["dmoz.org"]
    start_urls = [
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
    "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
    ]
    def parse(self, response):
        for sel in response.xpath("//ul[@class='directory-url']/li"):
            item = DmozItem()
            item['title'] = sel.xpath("a/text()").extract()
            item['link'] = sel.xpath("a/@href").extract()
            item['desc'] = sel.xpath("text()").extract()
            yield item

运行scrapy crawl dmoz , debug 信息说明我们已经抓取并填充成功:

Scrapy 简单的使用我们就介绍到这里, 如果想更深入使用Scrapy, 可以参考官方文档:

http://doc.scrapy.org/en/0.24/



-EOF-
睿初科技软件开发技术博客,转载请注明出处

blog comments powered by Disqus