文章目錄
Scrapy爬蟲
瞭解Scrapy爬蟲框架
Scrapy是一個爬蟲框架而非功能函數庫,簡單地說,它是一個半成品,可以幫助用戶簡單快速地部署一個專業的網絡爬蟲。Scrapy爬蟲框架主要由引擎(Engine)、調度器(Scheduler)、下載器(Downloader)、Spiders、Item Pipelines、下載器中間件(Downloader Middlewares)、Spider中間件(Spider Middlewares)這7個組件構成。
引擎(Engine)
引擎負責控制數據流在系統所有組件中的流向,並在不同的條件時觸發相對應的事件。這個組件相當於爬蟲的“大腦”,是整個爬蟲的調度中心
調度器(Scheduler)
調度器從引擎接受請求並將它們加入隊列,以便之後引擎需要它們時提供給引擎。初始爬取的URL和後續在網頁中獲取的待爬取的URL都將放入調度器中,等待爬取,同時調度器會自動去除重複的URL。如果特定的URL不需要去重也可以通過設置實現,如post請求的URL
下載器(Downloader)
下載器的主要功能是獲取網頁內容,提供給引擎和Spiders
Spiders
Spiders是Scrapy用戶編寫用於分析響應,並提取Items或額外跟進的URL的一個類。每個Spider負責處理一個(一些)特定網站
Item Pipelines
Item Pipelines主要功能是處理被Spiders提取出來的Items。典型的處理有清理、驗證及持久化(例如存取到數據庫中)。當網頁被爬蟲解析所需的數據存入Items後,將被髮送到項目管道(Pipelines),並經過幾個特定的次序處理數據,最後存入本地文件或數據庫
下載器中間件(Downloader Middlewares)
下載器中間件是一組在引擎及下載器之間的特定鉤子(specific hook),主要功能是處理下載器傳遞給引擎的響應(response)。下載器中間件提供了一個簡便的機制,通過插入自定義代碼來擴展Scrapy功能。通過設置下載器中間件可以實現爬蟲自動更換user-agent、IP等功能
Spider中間件(Spider Middlewares)
Spider中間件是一組在引擎及Spiders之間的特定鉤子(specific hook),主要功能是處理Spiders的輸入(響應)和輸出(Items及請求)。Spider中間件提供了一個簡便的機制,通過插入自定義代碼來擴展Scrapy功能。各組件之間的數據流向如圖所示
scrapy基本流程
- 引擎打開一個網站,找到處理該網站的Spiders,並向該Spiders請求第一個要爬取的URL。
- 引擎將爬取請求轉發給調度器,調度指揮進行下一步。
- 引擎向調度器獲取下一個要爬取的請求。
- 調度器返回下一個要爬取的URL給引擎,引擎將URL通過下載中間件(請求方向)轉發給下載器
- 一旦網頁下載完畢,下載器生成一個該網頁的響應,並將其通過下載中間件(返回響應方向)發送給引擎。
- 引擎從下載器中接收到響應並通過Spider中間件(輸入方向)發送給Spiders處理。
- Spiders處理響應並返回爬取到的Items及(跟進)新的請求給引擎。
- 引擎將(Spiders返回的)爬取到的Items給Item Pipeline,將(Spiders返回的)請求給調度器。
- 從第2步重複直到調度器中沒有更多的URL請求,引擎關閉該網站。
熟悉Scrapy常用命令
Scrapy通過命令行進行控制,Scrapy提供了多種命令,用於多種目的,並且每個命令都接收一組不同的參數和選項
除了全局命令外,Scrapy還提供了專用於項目的項目命令
創建Scrapy爬蟲項目
命令行下面使用
scrapy startproject <project_name> [project_dir]
這裏是引用
下面的步驟生成一TipDMSpider的爬蟲,爬取目標網頁
http://www.tipdm.org/bdrace/notices/
將內容保存到excel中
新建項目
命令行執行
scrapy startproject TipDMSpider
可以看到創建的文件夾內容如下
爬蟲的主要目標就是從網頁這一非結構化的數據源中提取結構化的數據。TipDMSpider項目最終的目標是解析出文章的標題(title)、時間(time)、正文(text)、瀏覽數(view_count)等數據。Scrapy提供Item對象來完成解析數據轉換爲結構化數據的功能。TipDMSpider項目提取的信息最終將存儲至csv文件與數據庫。使用pandas庫將Items中的數據轉換爲DataFrame會更方便處理
修改items腳本
爬蟲的主要目標就是從網頁這一非結構化的數據源中提取結構化的數據。TipDMSpider項目最終的目標是解析出文章的標題(title)、時間(time)、正文(text)、瀏覽數(view_count)等數據。Scrapy提供Item對象來完成解析數據轉換爲結構化數據的功能
分析目標頁需要獲取的內容如下
修改items的代碼如下
class TipdmspiderItem(scrapy.Item):
title = scrapy.Field()
time = scrapy.Field()
source = scrapy.Field()
text = scrapy.Field()
pass
修改setting
在setting下開啓ITEM_PIPELINES後才能使得到的items進入pipeline處理
ITEM_PIPELINES = {
'TipDMSpider.pipelines.TipdmspiderPipeline': 300,
}
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'C:\\Users\\zeng\\PycharmProjects\\spider\\TipDMSpider\\TipDMSpider'
項目的默認settings腳本中共有25個設置
修改piplines
items的內容將會流向piplines,piplines的作用就是講獲取到的數據進行清理、驗證後持久化
import pandas as pd
class TipdmspiderPipeline(object):
def process_item(self, item, spider):
data = pd.DataFrame(dict(item))
data.to_csv('output_data.csv', mode='a+', index=False, sep='|', header=False)
return item
編寫spider腳本
使用命令行在當前路徑下新建一個爬蟲入口文件
scrapy genspider tipdm www.tipdm.org
在spiders文件夾中可以看到生成出來的tipdm.py
定製中間件
中間件能夠完成很多任務,比如實現IP代理,改變下載頻率,限制最大爬取深度、篩選未成功響應、去重等等
定製下載器中間件
在項目中的middlewares.py文件中,是中間件的代碼,在最底下添加兩個class,是我們自定定義的中間件,第一個爲隨機代理,另外一個爲隨機ua
from scrapy.utils.python import to_bytes
from six.moves.urllib.parse import unquote
from six.moves.urllib.parse import urlunparse
import base64
import random
try:
from urllib2 import _parse_proxy
except ImportError:
from urllib.request import _parse_proxy
class ProxyMiddleware(object):
def _basic_auth_header(self, username, password):
user_pass = to_bytes(
'%s:%s' % (unquote(username), unquote(password)),
encoding='latin-1')
return base64.b64encode(user_pass).strip()
def process_request(self, request, spider):
PROXIES = [
"http://zeng:[email protected]:808/",
]
url = random.choice(PROXIES)
orig_type = ""
proxy_type, user, password, hostport = _parse_proxy(url)
proxy_url = urlunparse((proxy_type or orig_type, hostport, '', '', '', ''))
if user:
creds = self._basic_auth_header(user, password)
else:
creds = None
request.meta['proxy'] = proxy_url
print("當前使用代理:", url)
if creds:
request.headers['Proxy-Authorization'] = b'Basic ' + creds
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
user_agent_list = [
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
]
class UASpiderMiddleware(UserAgentMiddleware):
def __init__(self, user_agent):
self.user_agent = user_agent_list
# 設置User-Agent
def process_request(self, request, spider):
agent = random.choice(self.user_agent)
request.headers['User-Agent'] = agent
print('當前User-Agent:', agent)
激活中間件
在項目的setting.py文件中,還有很多其他信息,現在只需要關注以下幾個,首先激活剛加入的兩個自定義中間件,然後打開默認的一個refer中間件,找到DOWNLOADER_MIDDLEWARES,打開註釋,寫入如下內容
DOWNLOADER_MIDDLEWARES = {
'TipDMSpider.middlewares.TipdmspiderDownloaderMiddleware': 543,
'TipDMSpider.middlewares.ProxyMiddleware': 544,
'scrapy.spidermiddlewares.referer.DefaultReferrerPolicy': 545,
'TipDMSpider.middlewares.UASpiderMiddleware': 546,
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 0 # 關閉內置下載器中間件robots
}
聲明不遵守爬蟲協議,設置下載延遲爲5秒
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 5
打開緩存的註釋,並修改成如下
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'C:\\Users\\zeng\\PycharmProjects\\spider\\TipDMSpider\\TipDMSpider'
測試整個項目
在命令行執行
scrapy crawl tipdm
啓動爬蟲,可以看到中間的輸出,和最後的csv文件
題外話,使用自定義url去重
在項目中編寫一個去重的py文件,duplicate_filter.py,內容如下
from scrapy.dupefilter import BaseDupeFilter
from scrapy.utils.request import request_fingerprint
"""
1. 根據配置文件找到 DUPEFILTER_CLASS = 'xianglong.dupe.MyDupeFilter'
2. 判斷是否存在from_settings
如果有:
obj = MyDupeFilter.from_settings()
否則:
obj = MyDupeFilter()
"""
class MyDupeFilter(BaseDupeFilter):
def __init__(self):
self.record = set()
@classmethod
def from_settings(cls, settings):
return cls()
#:return: True表示已經訪問過;False表示未訪問過
def request_seen(self, request):
ident = request_fingerprint(request)
if ident in self.record:
print('已經訪問過了', request.url)
return True
print("=================新發現的url=================")
self.record.add(ident)
def open(self): # can return deferred
pass
def close(self, reason): # can return a deferred
pass
在setting中使用整個filter
DUPEFILTER_CLASS = 'TipDMSpider.duplicate_filter.MyDupeFilter'
在我們的tipdm.py中,也就是spider文件的入口,去掉
dont_filter=True
這樣去重class纔會生效
# 提取每一個分頁中的每一條新聞
def parse_url(self, response):
# 取出ul標籤
urls = response.xpath("//a[@class='titA']/@href").extract()
# 循環取出裏面的li鏈接
for url in urls:
article_url = "http://www.tipdm.org" + url
yield Request(article_url, callback=self.parse_item)
for url in urls:
article_url = "http://www.tipdm.org" + url
# yield Request(article_url, callback=self.parse_item, dont_filter=True)
yield Request(article_url, callback=self.parse_item)