1. Scrapy の紹介
Scrapy は、Web サイトのデータをクロールし、構造化データを抽出するために作成されたアプリケーション フレームワークです。 データマイニング、情報処理、履歴データの保存などの一連のプログラムで使用できます。
元々はページ スクレイピング (具体的には Web スクレイピング) 用に設計されましたが、API (Amazon Associates Web Services など) や一般的な Web クローラーによって返されたデータを取得するためにも使用できます。 Scrapy は広く使用されており、データマイニング、モニタリング、自動テストに使用できます
Scrapy は、Twisted 非同期ネットワーク ライブラリを使用してネットワーク通信を処理します。全体の構成は大まかに以下の通りです
Scrapy には主に次のコンポーネントが含まれています:
(1) エンジン (Scrapy): システム全体のデータフロー処理を処理し、トランザクションをトリガーするために使用されます (コアフレームワーク)
(2) スケジューラー (Scheduler): エンジンからリクエストを受け入れ、キューにプッシュし、エンジンが再度リクエストしたときに返すために使用されます。URL (キャプチャされた Web ページの URL またはリンク) として想像できます。 ) 優先キュー。次にクロールする URL を決定し、重複する URL を削除します
(3) ダウンローダー: Web コンテンツをダウンロードし、Web コンテンツをスパイダーに返すために使用されます (Scrapy ダウンローダーは、ツイストされた効率的な非同期モデルに基づいて構築されています)
(4) スパイダー: クローラーは主に、いわゆるエンティティ (アイテム) である特定の Web ページから必要な情報を抽出するために使用されます。ユーザーはそこからリンクを抽出し、Scrapy に次のページのクロールを継続させることもできます
プロジェクト パイプライン (パイプライン): クローラーによって Web ページから抽出されたエンティティの処理を担当します。主な機能は、エンティティを永続化し、エンティティの有効性を検証し、不要な情報を削除することです。ページがクローラーによって解析されると、プロジェクト パイプラインに送信され、データはいくつかの特定のシーケンスを通じて処理されます。
(5) ダウンローダーミドルウェア: Scrapy エンジンとダウンローダーの間にあるフレームワークで、主に Scrapy エンジンとダウンローダー間のリクエストと応答を処理します。
(6) Spider Middlewares: Scrapy エンジンとクローラーの間のフレームワーク。その主な仕事は、スパイダーの応答入力と要求出力を処理することです。
(7) スケジューラ ミドルウェア: Scrapy エンジンとスケジューラの間のミドルウェアで、Scrapy エンジンからスケジューラにリクエストと応答を送信します。
Scrapyの操作プロセスは大まかに以下のとおりです:
まず、エンジンは後続のクロールのためにスケジューラーからリンク (URL) を取り出します
エンジンは URL をリクエスト (Request) にカプセル化し、それをダウンローダーに渡します。ダウンローダーはリソースをダウンロードし、それを応答パケット (Response) にカプセル化します。
次に、クローラーはレスポンスを解析します
エンティティ (項目) が解析されると、さらなる処理のためにエンティティ パイプラインに渡されます。
解析されたリンク(URL)が取得できたら、そのURLをスケジューラに渡してクロールを待ちます
2. Scrapyをインストールします
次のコマンドを使用します:
3. スクレイピーチュートリアル
スクレイピングする前に、コードを保存するディレクトリを入力して、新しい Scrapy プロジェクトを作成する必要があります:
リーリー
(1)scrapy.cfg:プロジェクト設定ファイル
(2) チュートリアル/: プロジェクト Python モジュール。後でここにコードを追加します
(3)tutorial/items.py:プロジェクトアイテムファイル
(4)tutorial/pipelines.py: プロジェクトパイプラインファイル
(5)tutorial/settings.py: プロジェクト設定ファイル
(6)tutorial/spiders: スパイダーを配置するディレクトリ
3.1. アイテムを定義する
Items はスクレイピングされたデータをロードするコンテナです。Python の辞書のように機能しますが、スペルミスを防ぐために未定義のフィールドをパディングするなど、より多くの保護を提供します。
scrapy.Item クラスを作成し、scrapy.Field.型のクラス属性を定義することで、Item を宣言します
リーリー
Spider は、ドメイン (またはドメイン グループ) から情報をクロールするために使用されるユーザー作成のクラスで、ダウンロード用の URL の予備リスト、リンクをたどる方法、アイテムを抽出するためにこれらの Web ページのコンテンツを解析する方法を定義します。
Spider を構築するには、scrapy.Spider 基本クラスを継承し、3 つの主要な必須プロパティを特定します。
name:爬虫的识别名,它必须是唯一的,在不同的爬虫中你必须定义不同的名字.
start_urls:包含了Spider在启动时进行爬取的url列表。因此,第一个被获取到的页面将是其中之一。后续的URL则从初始的URL获取到的数据中提取。我们可以利用正则表达式定义和过滤需要进行跟进的链接。
parse():是spider的一个方法。被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
这个方法负责解析返回的数据、匹配抓取的数据(解析为 item )并跟踪更多的 URL。
在 /tutorial/tutorial/spiders 目录下创建 dmoz_spider.py
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)
3.3. 爬取
当前项目结构
├── scrapy.cfg └── tutorial ├── __init__.py ├── items.py ├── pipelines.py ├── settings.py └── spiders ├── __init__.py └── dmoz_spider.py
到项目根目录, 然后运行命令:
$ scrapy crawl dmoz
2014-12-15 09:30:59+0800 [scrapy] INFO: Scrapy 0.24.4 started (bot: tutorial) 2014-12-15 09:30:59+0800 [scrapy] INFO: Optional features available: ssl, http11 2014-12-15 09:30:59+0800 [scrapy] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'tutorial.spiders', 'SPIDER_MODULES': ['tutorial.spiders'], 'BOT_NAME': 'tutorial'} 2014-12-15 09:30:59+0800 [scrapy] INFO: Enabled extensions: LogStats, TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState 2014-12-15 09:30:59+0800 [scrapy] INFO: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware, MetaRefreshMiddleware, HttpCompressionMiddleware, RedirectMiddleware, CookiesMiddleware, ChunkedTransferMiddleware, DownloaderStats 2014-12-15 09:30:59+0800 [scrapy] INFO: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware 2014-12-15 09:30:59+0800 [scrapy] INFO: Enabled item pipelines: 2014-12-15 09:30:59+0800 [dmoz] INFO: Spider opened 2014-12-15 09:30:59+0800 [dmoz] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2014-12-15 09:30:59+0800 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023 2014-12-15 09:30:59+0800 [scrapy] DEBUG: Web service listening on 127.0.0.1:6080 2014-12-15 09:31:00+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None) 2014-12-15 09:31:00+0800 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None) 2014-12-15 09:31:00+0800 [dmoz] INFO: Closing spider (finished) 2014-12-15 09:31:00+0800 [dmoz] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 516, 'downloader/request_count': 2, 'downloader/request_method_count/GET': 2, 'downloader/response_bytes': 16338, 'downloader/response_count': 2, 'downloader/response_status_count/200': 2, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2014, 12, 15, 1, 31, 0, 666214), 'log_count/DEBUG': 4, 'log_count/INFO': 7, 'response_received_count': 2, 'scheduler/dequeued': 2, 'scheduler/dequeued/memory': 2, 'scheduler/enqueued': 2, 'scheduler/enqueued/memory': 2, 'start_time': datetime.datetime(2014, 12, 15, 1, 30, 59, 533207)} 2014-12-15 09:31:00+0800 [dmoz] INFO: Spider closed (finished)
3.4. 提取Items
3.4.1. 介绍Selector
从网页中提取数据有很多方法。Scrapy使用了一种基于 XPath 或者 CSS 表达式机制: Scrapy Selectors
出XPath表达式的例子及对应的含义:
等多强大的功能使用可以查看XPath tutorial
为了方便使用 XPaths,Scrapy 提供 Selector 类, 有四种方法 :
3.4.2. 取出数据
在查看网站源码后, 网站信息在第二个
<ul class="directory-url" style="margin-left:0;"> <li><a href="http://www.pearsonhighered.com/educator/academic/product/0,,0130260363,00%2Ben-USS_01DBC.html" class="listinglink">Core Python Programming</a> - By Wesley J. Chun; Prentice Hall PTR, 2001, ISBN 0130260363. For experienced developers to improve extant skills; professional level examples. Starts by introducing syntax, objects, error handling, functions, classes, built-ins. [Prentice Hall] <div class="flag"><a href="/public/flag?cat=Computers%2FProgramming%2FLanguages%2FPython%2FBooks&url=http%3A%2F%2Fwww.pearsonhighered.com%2Feducator%2Facademic%2Fproduct%2F0%2C%2C0130260363%2C00%252Ben-USS_01DBC.html"><img src="/img/flag.png" alt="Python クローラー プログラミング フレームワーク Scrapy 入門学習チュートリアル" title="report an issue with this listing"></a></div> </li> ...省略部分... </ul>
那么就可以通过一下方式进行提取数据
#通过如下命令选择每个在网站中的 <li> 元素: sel.xpath('//ul/li') #网站描述: sel.xpath('//ul/li/text()').extract() #网站标题: sel.xpath('//ul/li/a/text()').extract() #网站链接: sel.xpath('//ul/li/a/@href').extract()
如前所述,每个 xpath() 调用返回一个 selectors 列表,所以我们可以结合 xpath() 去挖掘更深的节点。我们将会用到这些特性,所以:
for sel in response.xpath('//ul/li') title = sel.xpath('a/text()').extract() link = sel.xpath('a/@href').extract() desc = sel.xpath('text()').extract() print title, link, desc
在已有的爬虫文件中修改代码
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): for sel in response.xpath('//ul/li'): title = sel.xpath('a/text()').extract() link = sel.xpath('a/@href').extract() desc = sel.xpath('text()').extract() print title, link, desc
3.4.3. 使用item
Item对象是自定义的python字典,可以使用标准的字典语法来获取到其每个字段的值(字段即是我们之前用Field赋值的属性)
>>> item = DmozItem() >>> item['title'] = 'Example title' >>> item['title'] 'Example title'
一般来说,Spider将会将爬取到的数据以 Item 对象返回, 最后修改爬虫类,使用 Item 来保存数据,代码如下
from scrapy.spider import Spider from scrapy.selector import Selector from tutorial.items import DmozItem class DmozSpider(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): sel = Selector(response) sites = sel.xpath('//ul[@class="directory-url"]/li') items = [] for site in sites: item = DmozItem() item['name'] = site.xpath('a/text()').extract() item['url'] = site.xpath('a/@href').extract() item['description'] = site.xpath('text()').re('-\s[^\n]*\\r') items.append(item) return items
3.5. 使用Item Pipeline
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
每个item pipeline组件(有时称之为ItemPipeline)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
编写你自己的item pipeline很简单,每个item pipeline组件是一个独立的Python类,同时必须实现以下方法:
(1)process_item(item, spider) #每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象,或是抛出 DropItem异常,被丢弃的item将不会被之后的pipeline组件所处理。
#参数:
item: 由 parse 方法返回的 Item 对象(Item对象)
spider: 抓取到这个 Item 对象对应的爬虫对象(Spider对象)
(2)open_spider(spider) #当spider被开启时,这个方法被调用。
#参数:
spider : (Spider object) – 被开启的spider
(3)close_spider(spider) #当spider被关闭时,这个方法被调用,可以再爬虫关闭后进行相应的数据处理。
#参数:
spider : (Spider object) – 被关闭的spider
为JSON文件编写一个items
from scrapy.exceptions import DropItem class TutorialPipeline(object): # put all words in lowercase words_to_filter = ['politics', 'religion'] def process_item(self, item, spider): for word in self.words_to_filter: if word in unicode(item['description']).lower(): raise DropItem("Contains forbidden word: %s" % word) else: return item
在 settings.py 中设置ITEM_PIPELINES激活item pipeline,其默认为[]
ITEM_PIPELINES = {'tutorial.pipelines.FilterWordsPipeline': 1}
3.6. 存储数据
使用下面的命令存储为json文件格式
scrapy crawl dmoz -o items.json
4.示例
4.1最简单的spider(默认的Spider)
用实例属性start_urls中的URL构造Request对象
框架负责执行request
将request返回的response对象传递给parse方法做分析
简化后的源码:
class Spider(object_ref): """Base class for scrapy spiders. All spiders must inherit from this class. """ name = None def __init__(self, name=None, **kwargs): if name is not None: self.name = name elif not getattr(self, 'name', None): raise ValueError("%s must have a name" % type(self).__name__) self.__dict__.update(kwargs) if not hasattr(self, 'start_urls'): self.start_urls = [] def start_requests(self): for url in self.start_urls: yield self.make_requests_from_url(url) def make_requests_from_url(self, url): return Request(url, dont_filter=True) def parse(self, response): raise NotImplementedError BaseSpider = create_deprecated_class('BaseSpider', Spider)
一个回调函数返回多个request的例子
import scrapyfrom myproject.items import MyItemclass MySpider(scrapy.Spider): name = 'example.com' allowed_domains = ['example.com'] start_urls = [ 'http://www.example.com/1.html', 'http://www.example.com/2.html', 'http://www.example.com/3.html', ] def parse(self, response): sel = scrapy.Selector(response) for h3 in response.xpath('//h3').extract(): yield MyItem(title=h3) for url in response.xpath('//a/@href').extract(): yield scrapy.Request(url, callback=self.parse)
构造一个Request对象只需两个参数: URL和回调函数
4.2CrawlSpider
通常我们需要在spider中决定:哪些网页上的链接需要跟进, 哪些网页到此为止,无需跟进里面的链接。CrawlSpider为我们提供了有用的抽象——Rule,使这类爬取任务变得简单。你只需在rule中告诉scrapy,哪些是需要跟进的。
回忆一下我们爬行mininova网站的spider.
class MininovaSpider(CrawlSpider): name = 'mininova' allowed_domains = ['mininova.org'] start_urls = ['http://www.mininova.org/yesterday'] rules = [Rule(LinkExtractor(allow=['/tor/\d+']), 'parse_torrent')] def parse_torrent(self, response): torrent = TorrentItem() torrent['url'] = response.url torrent['name'] = response.xpath("//h1/text()").extract() torrent['description'] = response.xpath("//div[@id='description']").extract() torrent['size'] = response.xpath("//div[@id='specifications']/p[2]/text()[2]").extract() return torrent
上面代码中 rules的含义是:匹配/tor/\d+的URL返回的内容,交给parse_torrent处理,并且不再跟进response上的URL。
官方文档中也有个例子:
rules = ( # 提取匹配 'category.php' (但不匹配 'subsection.php') 的链接并跟进链接(没有callback意味着follow默认为True) Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))), # 提取匹配 'item.php' 的链接并使用spider的parse_item方法进行分析 Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'), )
除了Spider和CrawlSpider外,还有XMLFeedSpider, CSVFeedSpider, SitemapSpider