使用scrapy抓取网站
====================
原文: `Crawl a website with scrapy `_
译者: `youngsterxyf `_
简介
------
本文中,我们将看到怎样从一个网站抓取信息,特别是,当所有页面具有共同的URL模式。我们将会看到怎样使用 `Scrapy `_ 做到这一点,Scrapy是一个非常强大却简单的网络爬虫框架。
例如,你也许感兴趣于抓取某个博客的每篇文章的信息,并将这些信息存储在数据库中。为了完成这样的一件事,我们将看到怎么使用 `Scrapy `_ 实现一个简单的 `网络爬虫 `_ 来抓取博客并将抽取的信息存入 `MongoDB `_ 数据库。
本文假设你有一个 `工作的MongoDB服务器 `_ ,并已安装 ``pymongo`` 和 ``scrapy`` ,这两个python包都可以使用 ``pip`` 进行安装。
如果你从来没玩过 `Scrapy `_ ,那么应该先读一下这篇 `简短的教程 `_ 。
第一步,识别URL模式
---------------------
本例中,我们将看到怎样从 `isbullsh.it `_ 的每篇博文中抽取如下信息:
- 标题(title)
- 作者(author)
- 标签(tag)
- 发布日期(release date)
- 统一资源定位符(url)
幸运地,所有的博文都有相同的URL模式: ``http://isbullsh.it/YYYY/MM/title`` 。这些链接可以从站点主页的不同页面上找到。
我们所需要的是一个爬虫,沿着符合这种模式的链接从目标网页上抓取需要的信息,验证数据的完整性,并存入MongoDB中。
构建网络爬虫
--------------
依据 `教程 `_ 的说明,创建一个Scrapy项目,得到如下所示的项目结构:
::
isbullshit_scraping/
|---isbullshit
| |--- __init__.py
| |--- items.py
| |--- pipelines.py
| |--- settings.py
| |___ spiders
| |--- __init__.py
| |--- isbullshit_spiders.py
|___scrapy.cfg
首先在 ``items.py`` 中定义包含抽取信息的项(item)的结构:
::
from scrapy.item import Item, Field
class IsBullshitItem(Item):
title = Field()
author = Field()
tag = Field()
date = Field()
link = Field()
然后在 ``isbullshit_spiders.py`` 中实现爬虫:
::
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractor.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from isbullshit.items import IsBullshitItem
class IsBullshitSpider(CrawlSpider):
name = 'isbullshit'
start_urls = ['http://isbullsh.it'] # urls from which the spider will start crawling
rules = [Rule(SgmlLinkExtractor(allow=[r'page/\d+']),follow=True),
# r'page/\d+' : regular expression for http://isbullsh.it/page/X URLs
Rule(SgmlLinkExtractor(allow=[r'\d{4}/\d{2}\w+']), callback='parse_blogpost')]
# r'\d{4}/\d{2}/\w+' : regular expression for http://isbullsh.it/YYYY/MM/title URLs
def parse_blogpost(self, response):
...
我们的爬虫继承自 ``CrawlSpider`` ,因为它"提供了一种便利的机制,通过定义一组规则来跟随链接"。更多的信息请看 `这里 `_ 。
然后定义了两个简单的规则:
- 跟随指向 ``http://isbullsh.it/page/X`` 的链接
- 使用回调方法 ``parse_blogpost`` 从URL模式 ``http://isbullsh.it/YYYY/MM/title`` 定义的页面中抽取信息
抽取数据
----------
为了从HTML代码中抽取标题,作者,等等,我们将使用 ``scrapy.selector.HtmlXPathSelector`` 对象,这个对象使用了 ``libxml2`` HTML语法分析器。如果你对这个对象不熟悉,那么应该读一读 ``XPathSelector`` `文档 `_ 。
现在我们在 ``parse_blogpost`` 方法中定义抽取的逻辑(这里仅定义标题和标签的抽取逻辑,因为其实逻辑基本上都是一样的):
::
def parse_blogpost(self, response):
hxs = HtmlXPathSelector(response)
item = IsBullshitItem()
# Extract title
item['title'] = hxs.select('//header/h1/text()').extract() # XPath selector for title
item['tag'] = hxs.select("//header/div[@class='post-data']/p/a/text()").extract() # Xpath selector for tag(s)
...
return item
**注解** : 为了确认你定义的XPath选择器(selectors),我建议使用Firebug,Firefox Inspect或者其他类似的工具来检查页面的HTML代码,然后在 `Scrapy shell `_ 中测试选择器,只有当数据位置与你要抓取的所有网页相一致时才有效。
结果存入MongoDB
-----------------
我们想要每次 `parse_blogpost` 方法返回的项都被发送到一个管道中进行数据验证并存入MongoDB中。
首先,我们需要在 ``settings.py`` 中加入一些东西:
::
ITEM_PIPELINES = ['isbullshit.pipelines.MongoDBPipeline',]
MONGODB_SERVER = "localhost"
MONGODB_PORT = 27017
MONGODB_DB = "isbullshit"
MONGODB_COLLECTION = "blogposts"
既然已经定义了管道,MongoDB数据库以及数据集合(collection),下面来看看管道的实现。我们希望确保没有任何缺失的数据(例如:没有标题,或者作者,或者其他信息的博文)。
如下就是 ``pipelines.py`` 文件的内容:
::
import pymongo
from scrapy.exception import DropItem
from scrapy.conf import settings
from scrapy import log
class MongoDBPipeline(object):
def __int__(self):
connection = pymongo.Connection(settings['MONGODB_SERVER'], settings['MONGODB_PORT'])
db = connection[settings['MONGODB_DB']]
self.collection = db[settings['MONGODB_COLLECTION']]
def process_item(self, item, spider):
valid = True
for data in item:
# here we only check if the data is not null
# but we could do any crazy validation we want
if not data:
valid = False
raise DropItem("Missing %s of blogpost from %s" % (data, item['url']))
if valid:
self.collection.insert(dict(item))
log.msg("Item wrote to MongoDB database %s/%s" %
(settings['MONGODB_DB'], settings['MONGODB_COLLECTION']),
level=log.DEBUG, spider=spider)
return item
发布爬虫
-------------
现在,我们需要做的就是切换到项目的根目录,执行:
::
$ scrapy crawl isbullshit
然后爬虫就会沿着所有指向博文的链接,检索博文的标题,作者名字,日期,等等,验证抽取的数据,如果顺利通过验证则把所有东西存入MongoDB的数据集合中。
相当简洁,是不是?
结论
-----
这个案例有点过分简单化了:所有的URL都有相似的模式,所有链接都是在HTML代码中硬编码的,没有涉及JS。在链接由JS代码生成的情况下,你可能会想试试 `Selenium `_ 库。你可以给爬虫添加新的规则或者更加复杂的正则表达式,但我只是想演示一下Scrapy是如何工作的,而不是陷入疯狂的正则表达式解说。
而且,请注意,有时,在玩玩网页抓取与 `惹上麻烦 `_ 之间只是一纸之隔。
最后,在玩网页抓取时,请谨记也许你是在使用大量的请求冲击服务器,有时这会造成你的IP被屏蔽。
请别成了一只 `鸭子(d*ick) `_