【原文鏈接】http://chenqx.github.io/2014/11/09/Scrapy-Tutorial-for-BBSSpider/
Scrapy Tutorial
接下來以爬取飲水思源BBS數據爲例來講述爬取過程,詳見 bbsdmoz代碼。
本篇教程中將帶您完成下列任務:
1. 創建一個Scrapy項目
2. 定義提取的Item
3. 編寫爬取網站的 spider 並提取 Item
4. 編寫 Item Pipeline 來存儲提取到的Item(即數據)
Creating a project
在開始爬取之前,您必須創建一個新的Scrapy項目。進入您打算存儲代碼的目錄中,運行下列命令:
scrapy startproject bbsdmoz
該命令將會創建包含下列內容的 bbsDmoz 目錄,這些文件分別是:
scrapy.cfg
: 項目的配置文件bbsDmoz/
: 該項目的python
模塊。之後您將在此加入代碼。bbsDmoz/items.py
: 項目中的item
文件.bbsDmoz/pipelines.py
: 項目中的pipelines
文件.bbsDmoz/settings.py
: 項目的設置文件.bbsDmoz/spiders/
: 放置spider
代碼的目錄.
Defining our Item
Item 是保存爬取到的數據的容器;其使用方法和python字典類似,並且提供了額外保護機制來避免拼寫錯誤導致的未定義字段錯誤。
類似在ORM (Object Relational Mapping, 對象關係映射) 中做的一樣,您可以通過創建一個 scrapy.Item
類,並且定義類型爲 scrapy.Field
的類屬性來定義一個Item。(如果不瞭解ORM,不用擔心,您會發現這個步驟非常簡單)
首先根據需要從bbs網站獲取到的數據對item進行建模。 我們需要從中獲取url,發帖板塊,發帖人,以及帖子的內容。 對此,在item中定義相應的字段。編輯 bbsDmoz 目錄中的 items.py
文件:
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
from scrapy.item import Item, Field
class BbsItem(Item):
# define the fields for your item here like:
# name = scrapy.Field()
url = Field()
forum = Field()
poster = Field()
content = Field()
一開始這看起來可能有點複雜,但是通過定義item, 您可以很方便的使用Scrapy的其他方法。而這些方法需要知道您的item的定義。
Our first Spider
Spider是用戶編寫用於從單個網站(或者一些網站)爬取數據的類。
其包含了一個用於下載的初始URL,如何跟進網頁中的鏈接以及如何分析頁面中的內容, 提取生成 item 的方法。
創建一個Spider
Selectors選擇器
我們使用XPath來從頁面的HTML源碼中選擇需要提取的數據。這裏給出XPath表達式的例子及對應的含義:
/html/head/title
: 選擇HTML文檔中<head>
標籤內的<title>
元素/html/head/title/text()
: 選擇上面提到的<title>
元素的文字//td
: 選擇所有的<td>
元素//div[@class="mine"]
: 選擇所有具有class="mine"
屬性的div
元素
以飲水思源BBS一頁面爲例:https://bbs.sjtu.edu.cn/bbstcon?board=PhD&reid=1406973178&file=M.1406973178.A
觀察HTML頁面源碼並創建我們需要的數據(種子名字,描述和大小)的XPath表達式。
通過觀察,我們可以發現poster
是包含在 pre/a
標籤中的,這裏是userid=jasperstream
:
因此可以提取jasperstream
的 XPath 表達式爲:'//pre/a/text()'
爲了配合XPath,Scrapy除了提供了 Selector 之外,還提供了方法來避免每次從response中提取數據時生成selector的麻煩。Selector有四個基本的方法:
xpath()
: 傳入xpath表達式,返回該表達式所對應的所有節點的selector list
列表 。css()
: 傳入CSS表達式,返回該表達式所對應的所有節點的selector list
列表.extract()
: 序列化該節點爲unicode字符串並返回list
。re()
: 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。
如提取上述的poster
的數據:
sel.xpath('//pre/a/text()').extract()
使用Item
Item
對象是自定義的python字典。您可以使用標準的字典語法來獲取到其每個字段的值(字段即是我們之前用Field賦值的屬性)。一般來說,Spider將會將爬取到的數據以 Item
對象返回。
Spider代碼
以下爲我們的第一個Spider代碼
,保存在 bbsDmoz/spiders
目錄下的 forumSpider.py
文件中:
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 20 13:18:58 2018
@author: Administrator
"""
from scrapy.selector import Selector
from scrapy.http import Request
from scrapy.contrib.spiders import CrawlSpider
from scrapy.contrib.loader import ItemLoader
#SGMLParser based link extractors are unmantained and its usage is discouraged. It is recommended to migrate to LxmlLinkExtractor
#from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.linkextractors.lxmlhtml import LxmlLinkExtractor
from bbsdmoz.items import BbsItem
class forumSpider(CrawlSpider):
# name of spiders
name = 'bbsSpider'
allow_domain = ['bbs.sjtu.edu.cn']
start_urls = [ 'https://bbs.sjtu.edu.cn/bbsall' ]
link_extractor = {
'page': LxmlLinkExtractor(allow = '/bbsdoc,board,\w+\.html$'),
'page_down': LxmlLinkExtractor(allow = '/bbsdoc,board,\w+,page,\d+\.html$'),
'content': LxmlLinkExtractor(allow = '/bbscon,board,\w+,file,M\.\d+\.A\.html$'),
}
_x_query = {
'page_content': '//pre/text()[2]',
'poster' : '//pre/a/text()',
'forum' : '//center/text()[2]',
}
def parse(self, response):
for link in self.link_extractor['page'].extract_links(response):
yield Request(url = link.url, callback=self.parse_page)
def parse_page(self, response):
for link in self.link_extractor['page_down'].extract_links(response):
yield Request(url = link.url, callback=self.parse_page)
for link in self.link_extractor['content'].extract_links(response):
yield Request(url = link.url, callback=self.parse_content)
def parse_content(self, response):
bbsItem_loader = ItemLoader(item=BbsItem(), response = response)
url = str(response.url)
bbsItem_loader.add_value('url', url)
bbsItem_loader.add_xpath('forum', self._x_query['forum'])
bbsItem_loader.add_xpath('poster', self._x_query['poster'])
bbsItem_loader.add_xpath('content', self._x_query['page_content'])
return bbsItem_loader.load_item()
Define Item Pipeline
當Item在Spider中被收集之後,它將會被傳遞到Item Pipeline,一些組件會按照一定的順序執行對Item的處理。
每個item pipeline組件(有時稱之爲“Item Pipeline”)是實現了簡單方法的Python類。他們接收到Item並通過它執行一些行爲,同時也決定此Item是否繼續通過pipeline,或是被丟棄而不再進行處理。
以下是item pipeline
的一些典型應用:
- 清理HTML數據
- 驗證爬取的數據(檢查item包含某些字段)
- 查重(並丟棄)
- 將爬取結果保存,如保存到數據庫、XML、JSON等文件中
編寫 Item Pipeline
編寫你自己的item pipeline
很簡單,每個item pipeline
組件是一個獨立的Python類,同時必須實現以下方法:
process_item(item, spider)
每個item pipeline組件都需要調用該方法,這個方法必須返回一個 Item (或任何繼承類)對象,或是拋出 DropItem異常,被丟棄的item將不會被之後的pipeline組件所處理。
參數:item (Item object) – 由 parse 方法返回的 Item 對象
spider (Spider object) – 抓取到這個 Item 對象對應的爬蟲對象
此外,他們也可以實現以下方法:
open_spider(spider)
當spider被開啓時,這個方法被調用。
參數: spider (Spider object) – 被開啓的spider
close_spider(spider)
當spider被關閉時,這個方法被調用,可以再爬蟲關閉後進行相應的數據處理。
參數: spider (Spider object) – 被關閉的spider
本文爬蟲的item pipeline
如下,保存爲XML文件:
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy import signals
from scrapy import log
from bbsdmoz.items import BbsItem
from twisted.enterprise import adbapi
from scrapy.contrib.exporter import XmlItemExporter
from dataProcess import dataProcess
class BbsdmozPipeline(object):
def __init__(self):
pass
@classmethod
def from_crawler(cls, crawler):
pipeline = cls()
crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
return pipeline
def spider_opened(self, spider):
self.file = open('bbsData.xml', 'wb')
self.expoter = XmlItemExporter(self.file)
self.expoter.start_exporting()
def spider_closed(self, spider):
self.expoter.finish_exporting()
self.file.close()
# process the crawled data, define and call dataProcess function
# dataProcess('bbsData.xml', 'text.txt')
def process_item(self, item, spider):
self.expoter.export_item(item)
return item
編寫dataProcess.py小工具:
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 20 14:45:01 2018
@author: Administrator
"""
from lxml import etree
# In Python 3, ConfigParser has been renamed to configparser
from configparser import ConfigParser
class dataProcess:
def __init__(self, source_filename, target_filename):
# load stop words into the memory.
fin = open(source_filename, 'r')
read = fin.read()
output = open(target_filename, 'w')
output.write(read)
fin.close()
output.close()
Settings (settings.py)
Scrapy設定(settings
)提供了定製Scrapy組件的方法。您可以控制包括核心(core),插件(extension),pipeline及spider組件。
設定爲代碼提供了提取以key-value映射的配置值的的全局命名空間(namespace)。 設定可以通過下面介紹的多種機制進行設置。
設定(settings)同時也是選擇當前激活的Scrapy項目的方法(如果您有多個的話)。
在setting
配置文件中,你可一定以抓取的速率、是否在桌面顯示抓取過程信息等。詳細請參考內置設定列表請參考 。
爲了啓用一個Item Pipeline
組件,你必須將它的類添加到 ITEM_PIPELINES
. 分配給每個類的整型值,確定了他們運行的順序,item按數字從低到高的順序,通過pipeline,通常將這些數字定義在0-1000範圍內。
# -*- coding: utf-8 -*-
# Scrapy settings for bbsdmoz project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://doc.scrapy.org/en/latest/topics/settings.html
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'bbsdmoz'
SPIDER_MODULES = ['bbsdmoz.spiders']
NEWSPIDER_MODULE = 'bbsdmoz.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'bbsdmoz (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'bbsdmoz.middlewares.BbsdmozSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'bbsdmoz.middlewares.BbsdmozDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'bbsdmoz.pipelines.BbsdmozPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
Crawling
寫好爬蟲程序後,我們就可以運行程序抓取數據。進入項目的根目錄bbsDomz/
下,執行下列命令啓動spider (會爬很久):
scrapy crawl bbsSpider
【完整代碼】
- dataProcess.py
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 20 14:45:01 2018
@author: Administrator
"""
from lxml import etree
# In Python 3, ConfigParser has been renamed to configparser
from configparser import ConfigParser
class dataProcess:
def __init__(self, source_filename, target_filename):
# load stop words into the memory.
fin = open(source_filename, 'r')
read = fin.read()
output = open(target_filename, 'w')
output.write(read)
fin.close()
output.close()
- settings.py
# -*- coding: utf-8 -*-
# Scrapy settings for bbsdmoz project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://doc.scrapy.org/en/latest/topics/settings.html
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'bbsdmoz'
SPIDER_MODULES = ['bbsdmoz.spiders']
NEWSPIDER_MODULE = 'bbsdmoz.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'bbsdmoz (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'bbsdmoz.middlewares.BbsdmozSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'bbsdmoz.middlewares.BbsdmozDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'bbsdmoz.pipelines.BbsdmozPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
- pipelines.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy import signals
from scrapy import log
from bbsdmoz.items import BbsItem
from twisted.enterprise import adbapi
from scrapy.contrib.exporter import XmlItemExporter
from dataProcess import dataProcess
class BbsdmozPipeline(object):
def __init__(self):
pass
@classmethod
def from_crawler(cls, crawler):
pipeline = cls()
crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
return pipeline
def spider_opened(self, spider):
self.file = open('bbsData.xml', 'wb')
self.expoter = XmlItemExporter(self.file)
self.expoter.start_exporting()
def spider_closed(self, spider):
self.expoter.finish_exporting()
self.file.close()
# process the crawled data, define and call dataProcess function
# dataProcess('bbsData.xml', 'text.txt')
def process_item(self, item, spider):
self.expoter.export_item(item)
return item
- items.py
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
from scrapy.item import Item, Field
class BbsItem(Item):
# define the fields for your item here like:
# name = scrapy.Field()
url = Field()
forum = Field()
poster = Field()
content = Field()
- forumSpider.py
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 20 13:18:58 2018
@author: Administrator
"""
from scrapy.selector import Selector
from scrapy.http import Request
from scrapy.contrib.spiders import CrawlSpider
from scrapy.contrib.loader import ItemLoader
#SGMLParser based link extractors are unmantained and its usage is discouraged. It is recommended to migrate to LxmlLinkExtractor
#from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.linkextractors.lxmlhtml import LxmlLinkExtractor
from bbsdmoz.items import BbsItem
class forumSpider(CrawlSpider):
# name of spiders
name = 'bbsSpider'
allow_domain = ['bbs.sjtu.edu.cn']
start_urls = [ 'https://bbs.sjtu.edu.cn/bbsall' ]
link_extractor = {
'page': LxmlLinkExtractor(allow = '/bbsdoc,board,\w+\.html$'),
'page_down': LxmlLinkExtractor(allow = '/bbsdoc,board,\w+,page,\d+\.html$'),
'content': LxmlLinkExtractor(allow = '/bbscon,board,\w+,file,M\.\d+\.A\.html$'),
}
_x_query = {
'page_content': '//pre/text()[2]',
'poster' : '//pre/a/text()',
'forum' : '//center/text()[2]',
}
def parse(self, response):
for link in self.link_extractor['page'].extract_links(response):
yield Request(url = link.url, callback=self.parse_page)
def parse_page(self, response):
for link in self.link_extractor['page_down'].extract_links(response):
yield Request(url = link.url, callback=self.parse_page)
for link in self.link_extractor['content'].extract_links(response):
yield Request(url = link.url, callback=self.parse_content)
def parse_content(self, response):
bbsItem_loader = ItemLoader(item=BbsItem(), response = response)
url = str(response.url)
bbsItem_loader.add_value('url', url)
bbsItem_loader.add_xpath('forum', self._x_query['forum'])
bbsItem_loader.add_xpath('poster', self._x_query['poster'])
bbsItem_loader.add_xpath('content', self._x_query['page_content'])
return bbsItem_loader.load_item()
The end.