架構流程詳解
整體架構及流程
架構目錄結構如圖,其中spider檔案下放完成爬取功能的spider,items下寫封裝資料的item,pipeline下寫過濾資料的item pipeline,settings為配置檔案:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CMyMWM5IWN3YWMwEjNlFzNwcjMhVWOlRDNzIzMxETO38CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
架構流程如圖:
頁面分析及腳手架
使用scrapy擷取網頁資訊
scrapy shell <url>
view(response)
然後使用chrome的開發者工具分析
建立scrapy腳手架
scrapy startproject <project_name>
cd <project_name>
scrapy genspider <spider_name> <domain>
spiders
自定義spider類必須繼承于scrapy.Spider類, 且必須覆寫parse方法處理response,spider會從start_requests(self)方法開始調用,預設使用start_urls清單構造Request對象, 回調函數為parse方法
回調函數的常用形式:
yield scrapy.Request(next_url, callback=self.parse)
spider中的allowed_domains=[]用于指定域名過濾url,對start_urls中的url不過濾,如果url中的域名不在allowed_domains中将不通路
對于頁面元素中連結的提取一般使用 LinkExtractor 類
LinkExtractor 類 構造函數接受的參數用于指定比對模式 正則比對用allow 和 deny; 域名比對用allow_domains 和 deny_domains xpath表達式 restrict_xpaths; css選擇器 restrict_css 指定标簽 tags; 指定屬性 attrs; 調用回調函數對連結進行處理 process_value
然後調用extract_links(self, response)用于傳回符合條件連結的絕對路徑
le = LinkExtractor(restrict_css='div.toctree-wrapper.compound', deny='/index.html$')
for link in le.extract_links(response):
yield scrapy.Request(link.url, callback=self.parse_example)
如果是用css等選擇器傳回的相對路徑, 可以調用 response.urljoin(rel_url)傳回絕對路徑
有時需要登入才能擷取需要的資訊,這時可以用FormRequest類模拟登入
# 通路登入頁面 得到response
def start_requests(self):
yield Request(self.login_url, callback=self.login)
# 調用FormRequest.from_response自動解析form表單并送出
def login(self, response):
fd = {'email': '[email protected]', 'password': 'mMj5xqRH2kamRXT'}
yield FormRequest.from_response(response, formdata=fd, callback=self.parse_login)
有驗證碼的網頁加載過程是先加載html内容,然後攜帶獲得的cookie再次發起HTTP請求加載驗證碼圖檔
# 加載登入頁面 獲得response
def start_requests(self):
yield Request(self.login_url, callback=self.login, dont_filter=True)
# 該方法既是登入頁面的解析函數,又是下載下傳驗證碼圖檔的響應處理函數
def login(self, response):
# 如果 response.mete['login_response'] 存在,目前 response 為驗證碼圖檔的響應
# 否則目前 response 為登入頁面的響應
login_response = response.meta.get('login_response')
if not login_response:
# Step 1:
# 此時 response 為登入頁面的響應,從中提取驗證碼圖檔的 url,下載下傳驗證碼圖檔,根據要爬取的網頁設定css選擇器
captchaUrl = response.css('div.login_right img::attr(src)').extract_first()
captchaUrl = response.urljoin(captchaUrl)
# 構造 Request 時,将目前 response 儲存到 meta 字典中 再次通路時跳轉到step 2
yield Request(captchaUrl, callback=self.login, meta={'login_response': response}, dont_filter=True)
else:
# Step 2
# 此時,response 為驗證碼圖檔的響應,response.body 是圖檔二進制資料
# login_response 為登入頁面的響應,用其構造表單請求并發送
formdata = {
'email': self.user,
'pass': self.password,
# 使用 OCR 識别 此處調用的是人工識别方法
'code': self.get_captcha_by_user(response.body),
}
yield FormRequest.from_response(login_response,callback=self.parse_login,formdata=formdata,
dont_filter=True)
人工識别驗證碼并輸入
def get_captcha_by_user(self, data):
# 人工識别
img = Image.open(BytesIO(data))
img.show()
captcha = input('輸入驗證碼:')
img.close()
return captcha
items.py
在items.py下定義封裝爬取到的資料的自定義item
class BookItem(scrapy.Item):
name = scrapy.Field(serializer=str)
自定義的item需要繼承自scrapy.Item類 其屬性用Field初始化 Field實則就是dict類 Field用于為字段添加中繼資料; 可通過item.fields獲得包含所有Field的字典, 進一步可通過字典方法獲得某個Field的中繼資料
seeting.py
1 導出檔案包含的字段 同時也決定了各字段的順序
FEED_EXPORT_FIELDS = ['price', 'name']
注: 該屬性隻會出現标出的字段
2 對資料進行過濾的Item pipeline
ITEM_PIPELINES = {
'scrapy_flow.pipelines.BookPipeline': 300,
}
3 導出檔案路徑, 其中%(name)s會替換成spider的name
FEED_URI = 'export_data/%(name)s.data'
4 導出資料格式, 内建支援的格式有JSON, JSON lines, CSV, XML, Pickle, Marshal, 還可以定義自己的Exporter
FEED_FORMAT = 'csv'
5 導出檔案編碼
FEED_EXPORT_ENCODING = 'utf-8'
6 自定義Exporter
FEED_EXPORTERS = {'excel': 'example.my_exporters.ExcelItemExporter'}
自定義Exporter定義方式
class ExcelItemExporter(BaseItemExporter):
# 每個子類必須實作 處理每項item
def export_item(self, item)
# 導出開始時被調用
def start_exporting(self)
# 導出完成時調用
def finish_exporting(self)
7 網站伺服器下有一個robots.txt, 決定爬蟲可以通路哪些網頁,scrapy預設開啟
ROBOTSTXT_OBEY = False
pipelines.py
定義在該子產品中的類主要用于對item進行處理
處理資料的item_pipeline類不需要繼承于特定基類, 但要實作process_item方法
def process_item(self, item, spider)
還有選用的方法如下:
#spider打開時回調, 通常完成資料庫連接配接等工作
def open_spider(self, spider)
#spider關閉時回調, 通常完成關閉資料庫連接配接等工作
def close_spider(self, spider)
#item pipeline建立時回調
def from_crawler(cls, crawler)
兩個内置的Item Pipeline, 用于下載下傳檔案和圖檔, 一般将其優先配置, 對于FilesPipeline, 将item中需要下載下傳的url放到item的file_urls字段, 在setting.py中配置FilesPipeline後會自動下載下傳
ImagesPipeline是FilesPipeline的子類,添加了一些針對圖檔的功能
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
}
IMAGES_STORE = 'download_images' # 指定圖檔下載下傳目錄
# 為圖檔生成縮略圖
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
# 通過尺寸過濾圖檔
IMAGES_MIN_WIDTH = 110
IMAGES_MIN_HEIGHT = 110
要修改FilesPipeline的下載下傳路徑或檔案名 可以覆寫FilesPipeline實作 也可以根據儲存的輸出(files字段包含了檔案名)來編寫腳本對檔案進行重命名,推薦使用前者來修改檔案名
# 儲存的輸出檔案 FilesPipeline會自動根據file_urls字段下載下傳檔案
{'file_urls': ['https://matplotlib.org/examples/pyplots/dollar_ticks.py'],
'files': [{'checksum': '3c3aaa6b64b130d59dc46fc225065087',
'path': 'full/f028022d9225a160bf7b25523994c5ada9f5896b.py',
'url': 'https://matplotlib.org/examples/pyplots/dollar_ticks.py'}]}
# 編寫item_pipeline覆寫該方法指定下載下傳檔案路徑
class MyFilesPipeline(FilesPipeline):
def file_path(self, request, response=None, info=None)
将爬取到的資料儲存到資料庫也可以使用item pipeline完成
# 定義一個儲存資料的pipeline
class MySQLPipeline:
# 該函數在爬取資料前被調用
def open_spider(self, spider):
db = spider.settings.get('MYSQL_DB_NAME', 'scrapy_db')
host = spider.settings.get('MYSQL_HOST', 'localhost')
port = spider.settings.get('MYSQL_PORT', 3306)
user = spider.settings.get('MYSQL_USER', 'root')
passwd = spider.settings.get('MYSQL_PASSWORD', 'password')
# self.dbpool = adbapi.ConnectionPool('MySQLdb', host=host, port=port,
# db=db, user=user, passwd=passwd, charset='utf8')
# 連接配接資料庫的對象
self.db_conn = MySQLdb.connect(host=host, port=port, db=db, user=user, passwd=passwd, charset='utf8')
# 執行資料庫語句的對象
self.db_cur = self.db_conn.cursor()
# 該函數在爬取完全部資料後被調用
def close_spider(self, spider):
# self.dbpool.close()
# 資料庫語句在着一次送出 縮短時間
self.db_conn.commit()
self.db_conn.close()
# 該函數對每一項資料都會調用
def process_item(self, item, spider):
# self.dbpool.runInteraction(self.insert_db, item)
self.insert_db(item)
return item
# def insert_db(self, tx, item):
def insert_db(self, item):
values = (
item['upc'],
item['name'],
item['price'],
item['review_rating'],
item['review_num'],
item['stock'],
)
sql = 'INSERT INTO books VALUES (%s, %s, %s, %s, %s, %s)'
self.db_cur.execute(sql, values)
# tx.execute(sql, values)
運作
運作爬蟲
scrapy crawl <scrapy_name> -t <format> -o <filename>
參考文獻: 《精通scrapy網絡爬蟲》劉碩著