原文: Crawl a website with scrapy
译者: youngsterxyf
本文中,我们将看到怎样从一个网站抓取信息,特别是,当所有页面具有共同的URL模式。我们将会看到怎样使用 Scrapy 做到这一点,Scrapy是一个非常强大却简单的网络爬虫框架。
例如,你也许感兴趣于抓取某个博客的每篇文章的信息,并将这些信息存储在数据库中。为了完成这样的一件事,我们将看到怎么使用 Scrapy 实现一个简单的 网络爬虫 来抓取博客并将抽取的信息存入 MongoDB 数据库。
本文假设你有一个 工作的MongoDB服务器 ,并已安装 pymongo 和 scrapy ,这两个python包都可以使用 pip 进行安装。
本例中,我们将看到怎样从 isbullsh.it 的每篇博文中抽取如下信息:
幸运地,所有的博文都有相同的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 中测试选择器,只有当数据位置与你要抓取的所有网页相一致时才有效。
我们想要每次 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的数据集合中。
相当简洁,是不是?