英文原文:Build a Website Crawler based upon Scrapy

标签: Scrapy Python
209人收藏此文章, 我要收藏renwofei423 推荐于
11个月前 (共 9 段, 翻译完成于 12-30) (14评
参与翻译(3人):

Scrapy是一个用于爬行网站以及在数据挖掘、信息处理和历史档案等大量应用范围内抽取结构化数据的应用程序框架,广泛用于工业。

在本文中我们将建立一个从Hacker News爬取数据的爬虫,并将数据按我们的要求存储在数据库中。

安装

我们将需要Scrapy以及 BeautifulSoup用于屏幕抓取,SQLAlchemy用于存储数据.

如果你使用ubuntu已经其他发行版的unix可以通过pip命令安装Scrapy。

1
pip install Scrapy

如果你使用Windows,你需要手工安装scrapy的一些依赖。

Windows用户需要pywin32、pyOpenSSL、Twisted、lxml和zope.interface。你可以下载这些包的编译版本来完成简易安装。

可以参照官方文档查看详情指导。

都安装好后,通过在python命令行下输入下面的命令验证你的安装:

1
2
>> import scrapy
>>

如果没有返回内容,那么你的安装已就绪。

安装HNScrapy

为了创建一个新项目,在终端里输入以下命令

1
$ scrapy startproject hn

这将会创建一系列的文件帮助你更容易的开始,cd 到 hn 目录然后打开你最喜欢的文本编辑器。

在items.py文件里,scrapy需要我们定义一个容器用于放置爬虫抓取的数据。如果你原来用过Django tutorial,你会发现items.py与Django中的models.py类似。

你将会发现class HnItem已经存在了,它继承自Item--一个scrapy已经为我们准备好的预定义的对象。

让我们添加一些我们真正想抓取的条目。我们给它们赋值为Field()是因为这样我们才能把元数据(metadata)指定给scrapy。

1
2
3
4
5
from scrapy.item import Item, Field
  
class HnItem(Item):
    title = Field()
    link = Field()

没什么难的--恩,就是这样。在scrapy里,没有别的filed类型,这点和Django不同。所以,我们和Field()杠上了。

scrapy的 Item类的行为类似于Python里面的dictionary,你能从中获取key和value。

开始写爬虫

在spiders文件夹下创建一个hn_spider.py文件。这是奇迹发生的地方--这正是我们告诉scrapy如何找到我们寻找的确切数据的地方。正如你所想的那样,一个爬虫只针对一个特定网页。它可能不会在其他网站上工作。

在ht_spider.py里,我们将定义一个类,HnSpider以及一些通用属性,例如name和urls。

首先,我们先建立HnSpider类以及一些属性(在类内部定义的变量,也被称为field)。我们将从scrapy的BaseSpider继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scrapy.spider import BaseSpider
from scrapy.selector import Selector
  
class HnSpider(BaseSpider):
    name = 'hn'
    allowed_domains = []
    start_urls = ['http://news.ycombinator.com']
  
    def parse(self, response):
        sel = Selector(response)
        sites = sel.xpath('//td[@class="title"]')
        for site in sites:
            title = site.xpath('a/text()').extract()
            link = site.xpath('a/@href').extract()
  
            print title, link

前面的几个变量是自解释的:name定义了爬虫的名字,allowed_domains列出了 供爬虫爬行的允许域名(allowed domain)的base-URL,start_urls 列出了爬虫从这里开始爬行的URL。后续的URL将从爬虫从start_urls下载的数据的URL开始。

接着,scrapy使用XPath选择器从网站获取数据--通过一个给定的XPath从HTML数据的特定部分进行选择。正如它们的文档所说,"XPath
是一种用于从XML选择节点的语言,它也可以被用于HTML"。你也可以阅读它们的文档了解更多关于XPath选择器的信息。

 

注意 在抓取你自己的站点并尝试计算 XPath 时, Chrome的 开发工具 提供了检查html元素的能力,
可以让你拷贝出任何你想要的元素的xpath. 它也提供了检测xpath的能力,只需要在javascript控制台中使用  $x, 例如 $x("//img"). 而在这个教程就不多深究这个了, Firefox 有一个插件, FirePath 同样也可以编辑,检查和生成XPath.

我们一般会基于一个定义好的Xpath来告诉 scrapy 到哪里去开始寻找数据. 让我们浏览我们的 Hacker News 站点,并右击选择”查看源代码“:

你会看到那个 sel.xpath('//td[@class="title"]')  有点貌似我们见过的HTML的代码. 从它们的 文档中你可以解读出构造XPath
并使用相对 XPath 的方法. 但本质上,  '//td[@class="title"]' 是在说: 所有的 <td> 元素中, 如果一个 <a class="title"></a> 被展现了出来,那就到  <td> 元素里面去寻找那个拥有一个被称作title的类型的<a>元素.

 

parse()方法使用了一个参数: response. 嘿,等一下 – 这个 self 是干什么的 – 看起来像是有两个参数!

每一个实体方法(在这种情况下, parse() 是一个实体方法 ) 接受一个对它自身的引用作为其第一个参数. 为了方便就叫做“self”.

response 参数是抓取器在像Hacker News发起一次请求之后所要返回的东西. 我们会用我们的XPaths转换那个响应.

现在我们将使用 BeautifulSoup 来进行转换. Beautiful Soup 将会转换任何你给它的东西 .

下载 BeautifulSoup 并在抓取器目录里面创建 soup.py 文件,将代码复制到其中.

在你的hn_spider.py文件里面引入beautifulSoup 和来自 items.py的 Hnitem,并且像下面这样修改转换方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from soup import BeautifulSoup as bs
from scrapy.http import Request
from scrapy.spider import BaseSpider
from hn.items import HnItem
  
class HnSpider(BaseSpider):
    name = 'hn'
    allowed_domains = []
    start_urls = ['http://news.ycombinator.com']
  
    def parse(self, response):
        if 'news.ycombinator.com' in response.url:
            soup = bs(response.body)
            items = [(x[0].text, x[0].get('href')) for in
                     filter(None, [
                         x.findChildren() for in
                         soup.findAll('td', {'class''title'})
                     ])]
  
            for item in items:
                print item
                hn_item = HnItem()
                hn_item['title'= item[0]
                hn_item['link'= item[1]
                try:
                    yield Request(item[1], callback=self.parse)
                except ValueError:
                    yield Request('http://news.ycombinator.com/' + item[1], callback=self.parse)
  
                yield hn_item

我们正在迭代这个items,并且给标题和链接赋上抓取来的数据.

 

现在就试试对Hacker News域名进行抓取,你会看到连接和标题被打印在你的控制台上.

1
scrapy crawl hn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
2013-12-12 16:57:06+0530 [scrapy] INFO: Scrapy 0.20.2 started (bot: hn)
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Optional features available: ssl, http11, django
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Overridden settings: {'NEWSPIDER_MODULE''hn.spiders''SPIDER_MODULES': ['hn.spiders'], 'BOT_NAME''hn'}
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Enabled extensions: LogStats, TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware
, MetaRefreshMiddleware, HttpCompressionMiddleware, RedirectMiddleware, CookiesMiddleware, ChunkedTransferMiddleware, DownloaderStats
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Enabled item pipelines:
2013-12-12 16:57:06+0530 [hn] INFO: Spider opened
2013-12-12 16:57:06+0530 [hn] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Telnet console listening on 0.0.0.0:6023
2013-12-12 16:57:06+0530 [scrapy] DEBUG: Web service listening on 0.0.0.0:6080
2013-12-12 16:57:07+0530 [hn] DEBUG: Redirecting (301) to <GET https://news.ycombinator.com/from <GET http://news.ycombinator.com>
2013-12-12 16:57:08+0530 [hn] DEBUG: Crawled (200) <GET https://news.ycombinator.com/> (referer: None)
(u'Caltech Announces Open Access Policy | Caltech', u'http://www.caltech.edu/content/caltech-announces-open-access-policy')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'Caltech Announces Open Access Policy | Caltech'}
(u'Coinbase Raises $25 Million From Andreessen Horowitz', u'http://blog.coinbase.com/post/69775463031/coinbase-raises-25-million-from-andreessen-horowitz')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'Coinbase Raises $25 Million From Andreessen Horowitz'}
(u'Backpacker stripped of tech gear at Auckland Airport', u'http://www.nzherald.co.nz/nz/news/article.cfm?c_id=1&objectid=11171475')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'Backpacker stripped of tech gear at Auckland Airport'}
(u'How I introduced a 27-year-old computer to the web', u'http://www.keacher.com/1216/how-i-introduced-a-27-year-old-computer-to-the-web/')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'How I introduced a 27-year-old computer to the web'}
(u'Show HN: Bitcoin Pulse - Tracking Bitcoin Adoption', u'http://www.bitcoinpulse.com')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
        {'link': u'http://www.bitcoinpulse.com',
         'title': u'Show HN: Bitcoin Pulse - Tracking Bitcoin Adoption'}
(u'Why was this secret?', u'http://sivers.org/ws')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
        {'link': u'http://sivers.org/ws''title': u'Why was this secret?'}
(u'PostgreSQL Exercises', u'http://pgexercises.com/')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
        {'link': u'http://pgexercises.com/''title': u'PostgreSQL Exercises'}
(u'What it feels like being an ipad on a stick on wheels', u'http://labs.spotify.com/2013/12/12/what-it-feels-like-being-an-ipad-on-a-stick-on-wheels/')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'What it feels like being an ipad on a stick on wheels'}
(u'Prototype ergonomic mechanical keyboards', u'http://blog.fsck.com/2013/12/better-and-better-keyboards.html')
2013-12-12 16:57:08+0530 [hn] DEBUG: Scraped from <200 https://news.ycombinator.com/>
         'title': u'Prototype ergonomic mechanical keyboards'}
.............
.............
.............
2013-12-12 16:58:41+0530 [hn] INFO: Closing spider (finished)
2013-12-12 16:58:41+0530 [hn] INFO: Dumping Scrapy stats:
        {'downloader/exception_count'2,
         'downloader/exception_type_count/twisted.internet.error.DNSLookupError'2,
         'downloader/request_bytes'22401,
         'downloader/request_count'71,
         'downloader/request_method_count/GET'71,
         'downloader/response_bytes'1482842,
         'downloader/response_count'69,
         'downloader/response_status_count/200'61,
         'downloader/response_status_count/301'4,
         'downloader/response_status_count/302'3,
         'downloader/response_status_count/404'1,
         'finish_reason''finished',
         'finish_time': datetime.datetime(20131212112841289000),
         'item_scraped_count'63,
         'log_count/DEBUG'141,
         'log_count/INFO'4,
         'request_depth_max'2,
         'response_received_count'62,
         'scheduler/dequeued'71,
         'scheduler/dequeued/memory'71,
         'scheduler/enqueued'71,
         'scheduler/enqueued/memory'71,
         'start_time': datetime.datetime(2013121211276843000)}
2013-12-12 16:58:41+0530 [hn] INFO: Spider closed (finished)

你将会在终端上看到大约400行的大量输出 ( 上面的输出之所以这么短,目的是为了方便观看 ).

你可以通过下面这个小命令将输出包装成JSON格式

1
$ scrapy crawl hn -o items.json -t json

现在我们已经基于正在找寻的项目实现了我们抓取器.

!

保存抓取到的数据

我们开始的步骤是创建一个保存我们抓取到的数据的数据库。打开 settings.py 并且像下面展现的代码一样定义数据库配置。

1
2
3
4
5
6
7
8
9
BOT_NAME = 'hn'
  
SPIDER_MODULES = ['hn.spiders']
NEWSPIDER_MODULE = 'hn.spiders'
  
DATABASE = {'drivername''xxx',
            'username''yyy',
            'password''zzz',
            'database''vvv'}

再在 hn 目录下创建一个 mdels.py 文件。我们将要使用SQLAlchemy作为ORM框架建立数据库模型。

首先,我们需要定义一个直接连接到数据库的方法。为此,我们需要引入 SQLAlchemy 以及settings.py文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.engine.url import URL
  
import settings
  
DeclarativeBase = declarative_base()
  
def db_connect():
    return create_engine(URL(**settings.DATABASE))
  
def create_hn_table(engine):
    DeclarativeBase.metadata.create_all(engine)
  
class Hn(DeclarativeBase):
    __tablename__ = "hn"
  
    id = Column(Integer, primary_key=True)
    title = Column('title', String(200))
    link = Column('link', String(200))

在开始下一步之前,我还想说明一下在 URL() 方法里两个星号的用法: **settings.DATABASE。首先,我们通过 settings.py 里的变量来访问数据库。这个 ** 实际上会取出所有在 DATABASE 路径下的值。URL 方法,一个在SQLAlchemy里定义的构造器,将会把key和value映射成一个SQLAlchemy能明白的URL来连接我们的数据库。

接着,URL() 方法将会解析其他元素,然后创建一个下面这样的将被 create_engine() 方法读取的URL。

接下来,我们要为我们的ORM创建一个表。我们需要 从 SQLAlchemy 引入declarative_base()以便把我们为表结构定义的类映射到Postgres上,以及一个从表的元数据里创建我们所需要的表的方法,还有我们已经定义好的用于存储数据的表和列。

!

管道管理

我们已经建立了用来抓取和解析HTML的抓取器, 并且已经设置了保存这些数据的数据库 . 现在我们需要通过一个管道来将两者连接起来.

打开 pipelines.py 并引入 SQLAlchemy的 sessionmaker 功能,用来绑定数据库 (创建那个连接), 当然也要引入我们的模型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sqlalchemy.orm import sessionmaker
from models import Hn, db_connect, create_hn_table
  
class HnPipeline(object):
    def __init__(self):
        engine = db_connect()
        create_hn_table(engine)
        self.Session = sessionmaker(bind=engine)
  
    def process_item(self, item, spider):
        session = self.Session()
        hn = Hn(**item)
        session.add(hn)
        session.commit()
        return item

我们在这里创建了一个类, HnPipeline(). 我们有一个构造器函数 def __init__(self) 来通过定义引擎初始化这个类, hn表格,还使用定义的这个引擎绑定/连接到数据库.

然后我们定义 _process_item() 来获取参数, _item_ 和 _spider_. 我们建立了一个同数据库的会话, 然后打开一个我们的Hn()模型中的数据项. 然后我们通过电泳session.add()来将 Hn
添加到我们的数据库中  – 在这一步, 它还没有被保存到数据库中 – 它仍然处于 SQLAlchemy 级别. 然后, 通过调用 session.commit(), 它就将被放入数据库中,过程也将会被提交 .

我们这里几乎还没有向 settings.py 中添加一个变量来告诉抓取器在处理数据时到哪里去找到我们的管道.

那就在 settings.py加入另外一个变量, ITEM_PIPELINES:

1
2
3
ITEM_PIPELINES = {
    'hn.pipelines.HnPipeline':300
}

这就是我们刚才所定义管道的目录/模块的路径.

现在我们就可以将所有抓取到的数据放到我们的数据库中, 让我们试试看我们获取到了什么,

再一次运行 crawl命令,并一直等到所有的处理过程完毕为止.

万岁!我们现在已经成功地把我们所抓取到的数据存入了数据库.

定时任务

如果我们不得不定期手动去执行这个脚本,那将会是很烦人的. 所有这里需要加入定时任务 .

定时任务将会在你指定的任何时间自动运行. 但是! 它只会在你的计算机处在运行状态时 (并不是在休眠或者关机的时候),并且特定于这段脚本需要是在和互联网处于联通状态时,才能运行. 为了不管你的计算机是出在何种状态都能运行这个定时任务, 你应该将 hn 代码 和bash 脚本,还有  cron 任务放在分开的将一直处在”运行“状态的服务器上伺服.

总结

这是有关抓取的最简短小巧的教程,而scrapy拥有提供高级功能和可用性的更多特性.

从 Github 下载整个源代码.

作者:leoking01 发表于2014-11-12 9:42:21 原文链接
阅读:6 评论:0 查看评论

[转]使用Scrapy建立一个网站抓取器的更多相关文章

  1. 使用scrapy框架来进行抓取的原因

    在python爬虫中:使用requests + selenium就可以解决将近90%的爬虫需求,那么scrapy就是解决剩下10%的吗? 这个显然不是这样的,scrapy框架是为了让我们的爬虫更强大. ...

  2. scrapy和selenium结合抓取动态网页

    1.安装python (我用的是2.7版本的) 2.安装scrapy:   详情请参考 http://blog.csdn.net/wukaibo1986/article/details/8167590 ...

  3. 基于scrapy的分布式爬虫抓取新浪微博个人信息和微博内容存入MySQL

    为了学习机器学习深度学习和文本挖掘方面的知识,需要获取一定的数据,新浪微博的大量数据可以作为此次研究历程的对象 一.环境准备   python 2.7  scrapy框架的部署(可以查看上一篇博客的简 ...

  4. [原创.数据可视化系列之十二]使用 nodejs通过async await建立同步数据抓取

    做数据分析和可视化工作,最重要的一点就是数据抓取工作,之前使用Java和python都做过简单的数据抓取,感觉用的很不顺手. 后来用nodejs发现非常不错,通过js就可以进行数据抓取工作,类似jqu ...

  5. 从urllib和urllib2基础到一个简单抓取网页图片的小爬虫

    urllib最常用的两大功能(个人理解urllib用于辅助urllib2) 1.urllib.urlopen() 2. urllib.urlencode()   #适当的编码,可用于后面的post提交 ...

  6. sciencedirect 网站抓取过程

      开发环境 C#+SQLite 软件使用教程: 设置页面 1.        首先录入需要查询的关键词,如果需要根据年去查询,可以勾选对应的年,支持多个年份查询.点击[设置关键字]按钮,把待查询关键 ...

  7. scrapy入门二(分页抓取文章入库)

    分页抓取博客园新闻,先从列表里分析下一页按钮 相关代码: # -*- coding: utf-8 -*- import scrapy from cnblogs.items import Article ...

  8. PHP登入网站抓取并且抓取数据

    有时候需要登入网站,然后去抓取一些有用的信息,人工做的话,太累了.有的人可以很快的做到登入,但是需要在登入后再去访问其他页面始终都访问不了,因为他们没有带Cookie进去而被当做是两次会话.下面看看代 ...

  9. Nutch2.1+mysql+solr3.6.1+中文网站抓取

    1.mysql 数据库配置 linux mysql安装步骤省略. 在首先进入/etc/my.cnf (mysql为5.1的话就不用修改my.cnf,会导致mysql不能启动)在[mysqld] 下添加 ...

随机推荐

  1. Codility Tree Height

    public class HeightOfTreeSolution { static int height=-1; public int solution(Tree T) { // write you ...

  2. 网站指纹识别工具——WhatWeb v0.4.7发布

      WhatWeb是一款网站指纹识别工具,主要针对的问题是:“这个网站使用的什么技术?”WhatWeb可以告诉你网站搭建使用的程序,包括何种CMS系统.什么博客系统.Javascript库.web服务 ...

  3. LINUX磁盘分区、格式化、挂载、卸载全程详解

    1.一切皆文件 Linux系统有一个理念:“一切皆文件”,所以计算机的硬件在linux中也是以“文件”的形式存在于/dev目录中.   图为CentOS 6.5系统中/dev目录的部分内容.不同的计算 ...

  4. EasyUI相关

    失去焦点事件 validType:'length[4,15]',events:{blur: function(){}} 添加自定义属性 $.extend($.fn.validatebox.defaul ...

  5. 关于小组所要做的APP的想法

    关于小组所要做的app,我们敲定下来是做关于在线做题的app,但是,纯粹的做题目的app我认为并没有什么大的吸引力,尤其是拿手机做题.所以,我们考虑准备在以下几个方面做功夫以增加吸引力.第一,我们的题 ...

  6. Visual Studio Code中文文档(一)-快速入门

    Visual Studio Code是一个轻量级但是十分强大的源代码编辑器,重要的是它在Windows, OS X 和Linux操作系统的桌面上均可运行.Visual Studio Code内置了对J ...

  7. Python面向对象高级之类的特殊成员

    上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段.方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能由类内部调用.无论人或事物往往都有不按套路出 ...

  8. 搭建一个分布式MongoDB鉴权集群

    今天休假在家,测试并搭建了一个replica set shard MongoDB鉴权集群.replica set shard 鉴权集群中文资料比较少,本文是个人笔记,同时也希望对后来者有所帮助.本文仅 ...

  9. blade and soul Personal Combos

    Personal Combos Since Blade and Soul is mainly based on skills, the game is more interesting after y ...

  10. 5、HTML5列表、块和布局

    1.块元素 块元素在显示的时候,通常会以新行开始 如:<h1> <p> <ul> <!-- 块—>注释 <p>hello</p> ...