常見爬蟲技術
1.降低請求頻率
對於Scrapy框架來說,在配置文件settings.py中設置DOWNLOAD_DELAY即可。以下代碼設置下載延遲時間爲3秒,即兩次請求間隔3秒。
DOWNLOAD_DELAY = 3
爲了防止請求過於規律,可以使用RANDOMIZE_DOWNLOAD_DELAY設置一個介於0.5 *DOWNLOAD_DELAY和1.5 *DOWNLOAD_DELAY之間的隨機延遲時間。
RANDOMIZE_DOWNLOAD_DELAY = True
2. 修改請求頭
網站可能會對HTTP請求頭的每個屬性做檢查。HTTP定義了十多個請求頭類型,不過大多數都不常用,只有幾個字段被大多數瀏覽器用來初始化所有的網絡請求,如下表所示。其中最重要的參數是User-Agent,我們使用它來僞裝成瀏覽器。如果你正在處理一個警覺性非常高的網站,就要注意那些經常用卻很少檢查的請求頭,如Accept-Language屬性。
3. 禁用Cookie
有些網站會通過Cookie來發現爬蟲的軌跡。因此,如果不是特殊需要,可以禁用Cookie,這樣網站就無法通過Cookie來偵測到爬蟲了。Scrapy中禁止Cookie功能也非常簡單,在配置文件settings.py中做如下設置:
COOKIES_ENABLED = False
4. 僞裝成隨機瀏覽器
我們都是通過User-Agent將爬蟲僞裝成固定瀏覽器,但是對於警覺性高的網站,會偵測到這一反常現象,即持續訪問網站的是同一種瀏覽器。因此,每次請求時,可以隨機僞裝成不同類型的瀏覽器。Scrapy中的中間件UserAgentMiddleware就是專門用於設置User-Agent的,在爬蟲運行時,會自動將User-Agent添加到HTTP請求中,並且可以設置多個瀏覽器,請求時可以隨機添加不同的瀏覽器。要實現此功能,只需要完成以下3步:
1)設定瀏覽器列表
MY_USER_AGENT = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"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",
]
將settings.py中設置固定瀏覽器代碼刪除或註釋掉,添加上面內容。
2)在中間件UserAgentMiddleware中從瀏覽器列表中隨機獲取一個瀏覽器
在middlewares.py中定義基於UserAgentMiddleware的類,實現對User-Agent的隨機設置,代碼如下:
#導入UserAgentMiddleware組件模塊
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random#導入隨機模塊
from xx.settings import MY_USER_AGENT#導入瀏覽器列表
#定義類xxUserAgentMiddleware,用於設置隨機設置user-agent
#繼承於UserAgentMiddleware
class xxUserAgentMiddleware(UserAgentMiddleware):
#處理Request請求函數
def process_request(self, request, spider):
#使用random模塊的choice函數從列表MY_USER_AGENT中隨機獲取一個瀏覽器類型
agent = random.choice(list(MY_USER_AGENT))
print("user-agent:",agent)
#將User-Agent附加到Reqeust對象的headers中
request.headers.setdefault('User-Agent', agent)
3)啓用中間件UserAgentMiddleware。
在settings.py中,啓用中間件xxtUserAgentMiddleware。
更簡單的辦法: 使用fake_useragent庫實現瀏覽器的隨機獲取
# 導入UserAgentMiddleware組件模塊
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
# 導入fake-useragent庫
from fake_useragent import UserAgent
# 定義類xxUserAgentMiddleware,用於設置隨機設置user-agent
class xxUserAgentMiddleware(UserAgentMiddleware):
#處理Request請求函數
def process_request(self, request, spider):
# 生成UserAgent對象
ua = UserAgent()
# 隨機獲取User-Agent
request.headers['User-Agent'] = ua.random
print(request.headers['User-Agent'])#打印
5.更換IP地址
有的網站會設置一個IP訪問頻率的閾值,一旦IP訪問頻率超過這個閾值,就會被認定爲機器人程序,進而封殺IP,禁止訪問網站的任何信息。一個很簡單的方法就是設置延時,但這顯然會降低爬蟲的效率,而IP地址又無法僞造。這時,就只能使用HTTP代理服務器了。
HTTP代理服務器(HTTP Proxy Server)其功能就是代理網絡用戶去取得網絡信息,它是客戶端瀏覽器和網站服務器之間的信息中轉站。
可以通過以下幾種方式獲取代理服務器:
-
自行搭建代理服務器
可以購買阿里雲或者騰訊雲服務器,自行搭建代理服務器。這種方式的優點是可靠、穩定;缺點是資金、時間和技術成本都比較高。 -
使用免費代理服務器
網絡上有許多免費的代理服務器供大家使用,搜索“代理”就能找到不少代理服務平臺,這些平臺一般都會提供免費代理服務器信息。這種方式的優點是免費、省心、省力;缺點是代理服務器有效期短、不穩定、不可控。 -
購買付費代理服務器
付費代理服務平衡了上述兩種方案,即在花費較少資金的情況下,提供可靠、穩定、時效較長的代理服務器。以下爲部分免費和付費代理服務平臺:
Scrapy設置代理服務器非常簡單,只需在Request請求中將代理服務器的URL賦給meta參數的鍵proxy。
Request(url,meta={"proxy": 'http://119.101.117.163:99999', "download_timeout": 10})
通過爬蟲,將西刺代理中的高匿代理服務器的URL爬取下來,經過驗證後,將有效的URL持久化到Redis數據庫中,提供給後續的爬蟲項目使用,主要代碼如下:
- 創建Scrapy項目
scrapy startproject xici_proxy
- 使用Item封裝數據
class XiciItem(scrapy.Item):
url = scrapy.Field()#url
cryptonym = scrapy.Field()#是否高匿名
- 創建Spider文件及Spider類
from scrapy import Request
from scrapy.spiders import Spider
from xx.items import XiciItem
from twisted.internet.error import DNSLookupError,TimeoutError, TCPTimedOutError#導入錯誤模塊
class XiciSpider(Spider):
name = 'xici'
current_page = 1 # 當前頁
def __init__(self,url):
self.test_url = url # 從命令中獲取測試網站的url
#獲取初始Request
def start_requests(self):
# 西祠代理免費代理的url地址
url = "https://www.xicidaili.com/nn"
yield Request(url)
# 數據解析
def parse(self, response):
list_selector = response.xpath("//tr[@class='odd']")
# 依次讀取每條代理的信息,從中獲取ip、端口、類型
for one_selector in list_selector:
item = XiciItem()
# 獲取ip
ip = one_selector.xpath("td[2]/text()").extract()[0]
# 獲取端口
port = one_selector.xpath("td[3]/text()").extract()[0]
# 獲取是否高匿
cryptonym = one_selector.xpath("td[5]/text()").extract()[0]
# 獲取類型(http或https)
http = one_selector.xpath("td[6]/text()").extract()[0]
# 拼接成完整的代理url
url = "{}://{}:{}".format(http,ip,port)
item["url"] = url
# 一定要設置dont_filter=True不過濾重複請求
yield Request(self.test_url,#測試網站的url
callback=self.test_parse,#回調函數
errback=self.error_back,#出錯回調函數
meta={"proxy":url,#代理服務器地址
"dont_retry":True,#請求不重試
"download_timeout":10,#超時時間
"item":item},
dont_filter=True#不過濾重複請求
)
if self.current_page <= 5:#爬取5頁代理信息
#獲取下一頁url
next_url = response.xpath("//a[@class='next_page']/@href").extract()[0]
next_url = response.urljoin(next_url)
self.current_page+=1
yield Request(next_url)
# 測試網站的數據解析
def test_parse(self, response):
yield response.meta["item"]
#請求失敗的回調函數
def error_back(self,failure):
#打印錯誤日誌信息
self.logger.error(repr(failure))
#細化出錯原因
if failure.check(DNSLookupError):# DNS出錯
# 獲取request
request = failure.request
#輸出錯誤日誌信息
self.logger.error('DNSLookupError on %s', request.url)
elif failure.check(TimeoutError, TCPTimedOutError):#超時出錯
# 獲取request
request = failure.request
#輸出錯誤日誌信息
self.logger.error('TimeoutError on %s', request.url)
- 使用Pipeline實現數據持久化
import redis
class XiciProxyPipeline(object):
# Spider開啓時,獲取數據庫配置信息,連接redis數據庫服務器
def open_spider(self, spider):
if spider.name == "xici":
# 獲取配置文件中redis配置信息
host = spider.settings.get("REDIS_HOST") # 主機地址
port = spider.settings.get("REDIS_PORT",) # 端口
db_index = spider.settings.get("REDIS_DB_INDEX") # 索引
db_psd = spider.settings.get("REDIS_PASSWORD") # 密碼
# 連接redis,得到一個連接對象
self.db_conn = redis.StrictRedis(host=host, port=port, db=db_index,
password=db_psd, decode_responses=True)
self.db_conn.delete("ip")
# 將數據存儲於redis數據庫
def process_item(self, item, spider):
if spider.name == "xici":
# 將item轉換爲字典類型
item_dict = dict(item)
# 將item_dict保存於key爲ip的集合中
self.db_conn.sadd("ip", item_dict["url"])
return item
- settings.py中配置項
# 1 設置robots協議:ROBOTSTXT_OBEY(False爲不遵守協議
ROBOTSTXT_OBEY = False
# 2 設置用戶代理:USER_AGENT
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0;Win64; x64) " \
"AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/68.0.3440.106 Safari/537.36"
# 3 啓用管道:XiciProxyPipeline
ITEM_PIPELINES = {
'xici_proxy.pipelines.XiciProxyPipeline': 300,
}
# 4 設置Redis數據庫地址、端口、索引及密碼信息
REDIS_HOST = "localhost" # 主機地址
REDIS_PORT = 6379 # 端口
REDIS_DB_INDEX = 0 # 索引
REDIS_PASSWORD = "" # 密碼
實現隨機代理
將免費可用的代理服務器信息保存到Redis數據庫後,所有的Scrapy爬蟲項目就可以使用它們了。需要注意的是,一個爬蟲項目的所有請求不能委託給固定的一個代理服務器,因爲目標網站依然會監測到同一IP頻繁訪問的異常現象。比較好的做法是每次請求時,隨機指定一個代理服務器,將請求分散到多個代理服務器中。
proxy = self.db_conn.srandmember("ip") # 隨機獲取一個代理url
print("隨機代理URL:", proxy)
# 根據URL生成Request,使用yield返回給引擎
yield Request(next_url,#目標URL
callback=self.qidian_parse, # 回調函數
errback=self.error_back, # 異常時調用的函數
meta={"proxy": proxy, # 代理服務器URL
"download_timeout": 10 # 超時時間
}
)