使用Scrapy建立一个网站抓取器



Build a Website Crawler based upon Scrapy


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 站点,并右击选择”查看源代码“:

使用Scrapy建立一个网站抓取器_第1张图片

你会看到那个 sel.xpath('//td[@class="title"]')  有点貌似我们见过的HTML的代码. 从它们的 文档中你可以解读出构造XPath 并使用相对 XPath 的方法. 但本质上,  '//td[@class="title"]' 是在说: 所有的 

 元素中, 如果一个  被展现了出来,那就到  元素里面去寻找那个拥有一个被称作title的类型的元素.

 

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  / / news.ycombinator.com / from  / / news.ycombinator.com>
2013 - 12 - 12  16 : 57 : 08 + 0530  [hn] DEBUG: Crawled ( 200 / / 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 / >
         { 'link' : u 'http://www.caltech.edu/content/caltech-announces-open-access-policy' ,
          '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 / >
         { 'link' : u 'http://blog.coinbase.com/post/69775463031/coinbase-raises-25-million-from-andreessen-horowitz' ,
          '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 / >
         { 'link' : u 'http://www.nzherald.co.nz/nz/news/article.cfm?c_id=1&objectid=11171475' ,
          '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 / >
         { 'link' : u 'http://www.keacher.com/1216/how-i-introduced-a-27-year-old-computer-to-the-web/' ,
          '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 / >
         { 'link' : u 'http://labs.spotify.com/2013/12/12/what-it-feels-like-being-an-ipad-on-a-stick-on-wheels/' ,
          '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 / >
         { 'link' : u 'http://blog.fsck.com/2013/12/better-and-better-keyboards.html' ,
          'title' : u 'Prototype ergonomic mechanical keyboards' }
(u 'H5N1' , u 'http://blog.samaltman.com/h5n1' )
.............
.............
.............
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( 2013 12 12 11 28 41 289000 ),
          '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( 2013 12 12 11 27 6 843000 )}
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  = 

你可能感兴趣的:(scrapy)