- 1.爬取數據后使用哪個數據庫存儲數據的,為什么?
- 2.你用過的爬蟲框架或者模塊有哪些?優缺點?
- 3.寫爬蟲是用多進程好?還是多線程好?
- 4.常見的反爬蟲和應對方法?
- 5.需要登錄的網頁,如何解決同時限制ip,cookie,session
- 6.驗證碼的解決?
- 7.“極驗”滑動驗證碼如何破解?
- 8.爬蟲多久爬一次,爬下來的數據是怎么存儲?
- 9.cookie過期的處理問題?
- 10.動態加載又對及時性要求很高怎么處理?
- 11.HTTPS有什么優點和缺點?
- 12.HTTPS是如何實現安全傳輸數據的?
- 13.談一談你對Selenium和PhantomJS了解
- 14.平常怎么使用代理的 ?
- 15.怎么監控爬蟲的狀態?
- 16.描述下scrapy框架運行的機制?
- 17.談談你對Scrapy的理解?
- 18.怎么樣讓 scrapy 框架發送一個 post 請求(具體寫出來)
- 19.怎么判斷網站是否更新?
- 20.圖片、視頻爬取怎么繞過防盜連接
- 21.你爬出來的數據量大概有多大?大概多長時間爬一次?
- 22.用什么數據庫存爬下來的數據?部署是你做的嗎?怎么部署?
- 23.增量爬取
- 24.爬取下來的數據如何去重,說一下scrapy的具體的算法依據。
- 25.Scrapy的優缺點?
- 26.怎么設置爬取深度?
- 27.scrapy和scrapy-redis有什么區別?為什么選擇redis數據庫?
- 28.分布式爬蟲主要解決什么問題?
- 29.什么是分布式存儲?
- 30.你所知道的分布式爬蟲方案有哪些?
- 31.scrapy-redis,有做過其他的分布式爬蟲嗎?
1.爬取數據后使用哪個數據庫存儲數據的,為什么?
MySQL
- 數據同步插入數據庫
在pipelines.py中引入數據庫連接模塊:
__init__是對數據進行初始化,定義連接信息如host,數據庫用戶名、密碼、數據庫名稱、數據庫編碼, 在process_item中進行插入數據操作,格式都是固定的
class MysqlPipeline(object):
def __init__(self):
self.conn = MySQLdb.connect('127.0.0.1', 'root', 'root', 'jobbole', charset='utf8', use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = 'INSERT INTO jobbole_article (`title`, `create_date`, `url`, `url_object_id`, `content`, `front_image_path`, `comment_nums`, `fav_nums`, `praise_nums`, `tags`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
self.cursor.execute(insert_sql, (item['title'], item['create_date'], item['url'], item['url_object_id'], item['content'], item["front_image_path"], item['comment_nums'], item['fav_nums'], item['praise_nums'], item['tags']))
self.conn.commit()
最后在settings.py中把MysqlPipelint()加入到系統中,需要注意的是優先級要小於之前加入處理圖片路徑的優先級
(先進行ArticleimagePipeline的處理,再進行MysqlPipeline處理)
ITEM_PIPELINES = {
'articlespider.pipelines.ArticlespiderPipeline': 300, #系統自動生成pipeline,未用
'articlespider.pipelines.ArticleimagePipeline': 1,
'articlespider.pipelines.MysqlPipeline': 4,
}
- 異步插入數據庫
異步操作需要引入twisted:
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors
class MysqlPipeline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host = settings["MYSQL_HOST"],
db = settings["MYSQL_DBNAME"],
user = settings["MYSQL_USER"],
passwd = settings["MYSQL_PASSWORD"],
charset='utf8',
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
# 使用twisted將mysql插入變成異步執行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error)
def handle_error(self, failure):
print(failure)
def do_insert(self, cursor, item):
# 具體執行插入
insert_sql = 'INSERT INTO jobbole_article (`title`, `create_date`, `url`, `url_object_id`, `content`, `front_image_path`, `comment_nums`, `fav_nums`, `praise_nums`, `tags`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
cursor.execute(insert_sql, (item['title'], item['create_date'], item['url'], item['url_object_id'], item['content'], item["front_image_path"], item['comment_nums'], item['fav_nums'], item['praise_nums'], item['tags']))
2.你用過的爬蟲框架或者模塊有哪些?優缺點?
1.Scrapy
Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架。 可以應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。。用這個框架可以輕松爬下來如亞馬遜商品信息之類的數據。
2.Beautiful Soup
Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工作時間。
3.寫爬蟲是用多進程好?還是多線程好?
IO密集型代碼(文件處理、網絡爬蟲等),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執行效率)。在實際的數據采集過程中,既考慮網速和響應的問題,也需要考慮自身機器的硬件情況,來設置多線程或多進程
4.常見的反爬蟲和應對方法?
從功能上來講,爬蟲一般分為數據采集,處理,儲存三個部分。這里我們只討論數據采集部分。
一般網站從三個方面反爬蟲:用戶請求的Headers,用戶行為,網站目錄和數據加載方式。前兩種比較容易遇到,大多數網站都從這些角度來反爬蟲。第三種一些應用ajax的網站會采用,這樣增大了爬取的難度。
0x02 通過Headers反爬蟲
從用戶請求的Headers反爬蟲是最常見的反爬蟲策略。很多網站都會對Headers的User-Agent進行檢測,還有一部分網站會對Referer進行檢測(一些資源網站的防盜鏈就是檢測Referer)。如果遇到了這類反爬蟲機制,可以直接在爬蟲中添加Headers,將瀏覽器的User-Agent復制到爬蟲的Headers中;或者將Referer值修改為目標網站域名。對於檢測Headers的反爬蟲,在爬蟲中修改或者添加Headers就能很好的繞過。
0x03 基於用戶行為反爬蟲
還有一部分網站是通過檢測用戶行為,例如同一IP短時間內多次訪問同一頁面,或者同一賬戶短時間內多次進行相同操作。
大多數網站都是前一種情況,對於這種情況,使用IP代理就可以解決。可以專門寫一個爬蟲,爬取網上公開的代理ip,檢測后全部保存起來。這樣的代理ip爬蟲經常會用到,最好自己准備一個。有了大量代理ip后可以每請求幾次更換一個ip,這在requests或者urllib2中很容易做到,這樣就能很容易的繞過第一種反爬蟲。
對於第二種情況,可以在每次請求后隨機間隔幾秒再進行下一次請求。有些有邏輯漏洞的網站,可以通過請求幾次,退出登錄,重新登錄,繼續請求來繞過同一賬號短時間內不能多次進行相同請求的限制。
0x04 動態頁面的反爬蟲
上述的幾種情況大多都是出現在靜態頁面,還有一部分網站,我們需要爬取的數據是通過ajax請求得到,或者通過JavaScript生成的。首先用Firebug或者HttpFox對網絡請求進行分析。如果能夠找到ajax請求,也能分析出具體的參數和響應的具體含義,我們就能采用上面的方法,直接利用requests或者urllib2模擬ajax請求,對響應的json進行分析得到需要的數據。
能夠直接模擬ajax請求獲取數據固然是極好的,但是有些網站把ajax請求的所有參數全部加密了。我們根本沒辦法構造自己所需要的數據的請求。我這幾天爬的那個網站就是這樣,除了加密ajax參數,它還把一些基本的功能都封裝了,全部都是在調用自己的接口,而接口參數都是加密的。遇到這樣的網站,我們就不能用上面的方法了,我用的是selenium+phantomJS框架,調用瀏覽器內核,並利用phantomJS執行js來模擬人為操作以及觸發頁面中的js腳本。從填寫表單到點擊按鈕再到滾動頁面,全部都可以模擬,不考慮具體的請求和響應過程,只是完完整整的把人瀏覽頁面獲取數據的過程模擬一遍。
用這套框架幾乎能繞過大多數的反爬蟲,因為它不是在偽裝成瀏覽器來獲取數據(上述的通過添加 Headers一定程度上就是為了偽裝成瀏覽器),它本身就是瀏覽器,phantomJS就是一個沒有界面的瀏覽器,只是操控這個瀏覽器的不是人。利用 selenium+phantomJS能干很多事情,例如識別點觸式(12306)或者滑動式的驗證碼,對頁面表單進行暴力破解等等。它在自動化滲透中還 會大展身手,以后還會提到這個。
5.需要登錄的網頁,如何解決同時限制ip,cookie,session
解決限制IP可以使用代理IP地址池、服務器;不適用動態爬取的情況下可以使用反編譯JS文件獲取相應的文件,或者換用其它平台(比如手機端)看看是否可以獲取相應的json文件。
6.驗證碼的解決?
1.輸入式驗證碼
解決思路:這種是最簡單的一種,只要識別出里面的內容,然后填入到輸入框中即可。這種識別技術叫OCR,這里我們推薦使用Python的第三方庫,tesserocr。對於沒有什么背影影響的驗證碼如圖2,直接通過這個庫來識別就可以。但是對於有嘈雜的背景的驗證碼這種,直接識別識別率會很低,遇到這種我們就得需要先處理一下圖片,先對圖片進行灰度化,然后再進行二值化,再去識別,這樣識別率會大大提高。
驗證碼識別大概步驟
- 轉化成灰度圖
- 去背景噪聲
- 圖片分割
2.滑動式驗證碼
解決思路:對於這種驗證碼就比較復雜一點,但也是有相應的辦法。我們直接想到的就是模擬人去拖動驗證碼的行為,點擊按鈕,然后看到了缺口 的位置,最后把拼圖拖到缺口位置處完成驗證。
第一步:點擊按鈕。然后我們發現,在你沒有點擊按鈕的時候那個缺口和拼圖是沒有出現的,點擊后才出現,這為我們找到缺口的位置提供了靈感。
第二步:拖到缺口位置。我們知道拼圖應該拖到缺口處,但是這個距離如果用數值來表示?通過我們第一步觀察到的現象,我們可以找到缺口的位置。這里我們可以比較兩張圖的像素,設置一個基准值,如果某個位置的差值超過了基准值,那我們就找到了這兩張圖片不一樣的位置,當然我們是從那塊拼圖的右側開始並且從左到右,找到第一個不一樣的位置時就結束,這是的位置應該是缺口的left,所以我們使用selenium拖到這個位置即可。這里還有個疑問就是如何能自動的保存這兩張圖?這里我們可以先找到這個標簽,然后獲取它的location和size,然后 top,bottom,left,right = location['y'] ,location['y']+size['height']+ location['x'] + size['width'] ,然后截圖,最后摳圖填入這四個位置就行。具體的使用可以查看selenium文檔,點擊按鈕前摳張圖,點擊后再摳張圖。最后拖動的時候要需要模擬人的行為,先加速然后減速。因為這種驗證碼有行為特征檢測,人是不可能做到一直勻速的,否則它就判定為是機器在拖動,這樣就無法通過驗證了。
3.點擊式的圖文驗證 和 圖標選擇
圖文驗證:通過文字提醒用戶點擊圖中相同字的位置進行驗證。
圖標選擇: 給出一組圖片,按要求點擊其中一張或者多張。借用萬物識別的難度阻擋機器。
這兩種原理相似,只不過是一個是給出文字,點擊圖片中的文字,一個是給出圖片,點出內容相同的圖片。
這兩種沒有特別好的方法,只能借助第三方識別接口來識別出相同的內容,推薦一個超級鷹,把驗證碼發過去,會返回相應的點擊坐標。
然后再使用selenium模擬點擊即可。具體怎么獲取圖片和上面方法一樣。
7.“極驗”滑動驗證碼如何破解?
破解核心思路:
1、如何確定滑塊滑動的距離?
滑塊滑動的距離,需要檢測驗證碼圖片的缺口位置
滑動距離 = 終點坐標 - 起點坐標
然后問題轉化為我們需要屏幕截圖,根據selenium中的position方法並進行一些坐標計算,獲取我們需要的位置
2、坐標我們如何獲取?
起點坐標:
每次運行程序,位置固定不變,滑塊左邊界離驗證碼圖片左邊界有6px的距離
終點坐標:
每次運行程序,位置會變,我們需要計算每次缺口的位置
怎么計算終點也就是缺口的位置?
先舉個例子,比如我下面兩個圖片都是120x60的圖片,一個是純色的圖片,一個是有一個藍色線條的圖片(藍色線條位置我事先設定的是60px位置),我現在讓你通過程序確定藍色線條的位置,你怎么確定?
答案:
遍歷所有像素點色值,找出色值不一樣的點的位置來確定藍色線條的位置
這句話該怎么理解?大家點開我下面的圖片,是不是發現圖片都是由一個一個像素點組成的,120×60的圖片,對應的像素就是橫軸有120個像素點,縱軸有60個像素點,我們需要遍歷兩個圖片的坐標並對比色值,從(0,0)(0,1)......一直到(120,60),開始對比兩個圖片的色值,遇到色值不一樣的,我們return返回該位置即可
8.爬蟲多久爬一次,爬下來的數據是怎么存儲?
- 以json格式存儲到文本文件
這是最簡單,最方便,最使用的存儲方式,json格式保證你在打開文件時,可以直觀的檢查所存儲的數據,一條數據存儲一行,這種方式適用於爬取數據量比較小的情況,后續的讀取分析也是很方便的。 - 存儲到excel
如果爬取的數據很容易被整理成表格的形式,那么存儲到excel是一個比較不錯的選擇,打開excel后,對數據的觀察更加方便,excel也可以做一些簡單的操作,寫excel可以使用xlwt這個庫,讀取excel可以使用xlrd,同方法1一樣,存儲到excel里的數據不宜過多,此外,如果你是多線程爬取,不可能用多線程去寫excel,這是一個限制。 - 存儲到sqlite
sqlite無需安裝,是零配置數據庫,這一點相比於mysql要輕便太多了,語法方面,只要你會mysql,操作sqlite就沒有問題。當爬蟲數據量很大時,需要持久化存儲,而你又懶得安裝mysql時,sqlite絕對是最佳選擇,不多呢,它不支持多進程讀寫,因此不適合多進程爬蟲。 - 存儲到mysql數據庫
mysql可以遠程訪問,而sqlite不可以,這意味着你可以將數據存儲到遠程服務器主機上,當數據量非常大時,自然要選擇mysql而不是sqlite,但不論是mysql還是sqlite,存儲數據前都要先建表,根據要抓取的數據結構和內容,定義字段,這是一個需要耐心和精力的事情。 - 存儲到mongodb
我最喜歡no sql 數據庫的一個原因就在於不需要像關系型數據庫那樣去定義表結構,因為定義表結構很麻煩啊,要確定字段的類型,varchar 類型數據還要定義長度,你定義的小了,數據太長就會截斷。
mongodb 以文檔方式存儲數據,你使用pymongo這個庫,可以直接將數據以json格式寫入mongodb, 即便是同一個collection,對數據的格式也是沒有要求的,實在是太靈活了。
剛剛抓下來的數據,通常需要二次清洗才能使用,如果你用關系型數據庫存儲數據,第一次就需要定義好表結構,清洗以后,恐怕還需要定義個表結構,將清洗后的數據重新存儲,這樣過於繁瑣,使用mongodb,免去了反復定義表結構的過程。
9.cookie過期的處理問題?
這時候就需要cookie自動的更新了。通常怎樣自動更新cookie呢?這里會用到selenium。
步驟1、 采用selenium自動登錄獲取cookie,保存到文件;
步驟2、 讀取cookie,比較cookie的有效期,若過期則再次執行步驟1;
步驟3、 在請求其他網頁時,填入cookie,實現登錄狀態的保持。
import os
import pickle
import time
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium import webdriver
def get_cookie_from_network():
# 這個url比較關鍵,網絡上比較老的辦法是
# url_login = 'https://login.taobao.com/member/login.jhtml'
# 測試之后發現,會報錯“可能不是一個可交互的element”
# 在后面添加?style=mini后就可以了
url_login = 'https://login.taobao.com/member/login.jhtml?style=mini'
# 這一段是為了給selenium添加user-agent。模擬瀏覽器
dcap = dict(DesiredCapabilities.PHANTOMJS)
dcap["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:25.0) Gecko/20100101 Firefox/25.0 ")
driver = webdriver.PhantomJS(desired_capabilities=dcap)
driver.implicitly_wait(1)
driver.get(url_login)
# driver.find_element_by_id("J_Static2Quick").click()
driver.find_element_by_id("TPL_username_1").clear()
driver.find_element_by_id("TPL_password_1").clear()
driver.find_element_by_id('TPL_username_1').send_keys(u'張小呆920318')
driver.find_element_by_id('TPL_password_1').send_keys('plus820828')
driver.find_element_by_id('J_SubmitStatic').click()
# 獲得 cookie信息
cookie_list = driver.get_cookies()
print cookie_list
cookie_dict = {}
for cookie in cookie_list:
#寫入文件
f = open('cookies/' + cookie['name']+'.taobao','w')
pickle.dump(cookie, f)
f.close()
if cookie.has_key('name') and cookie.has_key('value'):
cookie_dict[cookie['name']] = cookie['value']
return cookie_dict
將獲取的cookies存儲在文件中。后期,你可以把它封裝成一個函數放在爬蟲的__init__中,也可以利用linux的crontab來定時獲取最新的cookies值。
10.動態加載又對及時性要求很高怎么處理?
- Selenium+Phantomjs
- 盡量不使用 sleep 而使用 WebDriverWait
11.HTTPS有什么優點和缺點?
- 優點:相比於http,https可以提供更加優質保密的信息,保證了用戶數據的安全性,此外https同時也一定程度上保護了服務端,使用惡意攻擊和偽裝數據的成本大大提高。
- 缺點:缺點也同樣很明顯,第一https的技術門檻較高,多數個人或者私人網站難以支撐,CA機構頒發的證書都是需要年費的,此外對接Https協議也需要額外的技術支持;其二,目前來說大多數網站並不關心數據的安全性和保密性,其https最大的優點對它來說並不適用;其三,https加重了服務端的負擔,相比於http其需要更多的資源來支撐,同時也降低了用戶的訪問速度;第四,目前來說Http網站仍然大規模使用,在瀏覽器側也沒有特別大的差別,很多用戶不關心的話根本不感知。
12.HTTPS是如何實現安全傳輸數據的?
- 首先https的服務端必須要擁有一個CA認證合法授權的證書,沒有這個證書,客戶端在訪問該服務器時就會提醒用戶這個網站是不受信任的。只有通過CA認證的服務器才是可靠的,這保證了用戶在訪問服務器的安全性。瀏覽器會保持一個信任的CA機構列表,通過這些機構出查詢所訪問的服務器提供的證書是否合法。
- 如果此時發現證書是合法OK的,那么就從這個服務器端的證書中獲取到了加密秘鑰,這個加密秘鑰會溝通商議出一個隨機的對稱秘鑰,服務端在傳輸信息使用該秘鑰進行加密。而客戶端在收到這部分信息后,在瀏覽器側通過之前得到的對稱秘鑰進行解密,相反如果客戶端想要向服務端發送消息時也是如此。
13.談一談你對Selenium和PhantomJS了解
selenium
Selenium是一個用於Web應用程序測試的工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等主流瀏覽器。這個工具的主要功能包括:測試與瀏覽器的兼容性——測試你的應用程序看是否能夠很好得工作在不同瀏覽器和操作系統之上。
它的功能有:
框架底層使用JavaScript模擬真實用戶對瀏覽器進行操作。測試腳本執行時,瀏覽器自動按照腳本代碼做出點擊,輸入,打開,驗證等操作,就像真實用戶所做的一樣,從終端用戶的角度測試應用程序。
使瀏覽器兼容性測試自動化成為可能,盡管在不同的瀏覽器上依然有細微的差別。
使用簡單,可使用Java,Python等多種語言編寫用例腳本
也就是說,它可以根據指令,做出像真實的人在訪問瀏覽器一樣的動作,比如打開網頁,截圖等功能。
phantomjs
(新版本的selenium已經開始棄用phantomjs, 不過有時候我們可以單獨用它做一些事情)
是一個基於Webkit的無界面瀏覽器,可以把網站內容加載到內存中並執行頁面上的各種腳本(比如js)。
14.平常怎么使用代理的 ?
proxies = {'http':'http://10.10.10.10:8765','https':'https://10.10.10.10:8765'}
resp = requests.get(url,proxies = proxies)
注:免費的代理IP可以在這個網站上獲取:http://www.xicidaili.com/nn/
15.怎么監控爬蟲的狀態?
- 使用 python 的 STMP 包將爬蟲的狀態信心發送到指定的郵箱
- Scrapyd、pyspider
- 引入日志
集成日志處理平台來進行監控,如 elk
16.描述下scrapy框架運行的機制?
從start_urls里獲取第一批url並發送請求,請求由引擎交給調度器入請求隊列,獲取完畢后,調度器將請求隊列里的請求交給下載器去獲取請求對應的響應資源,並將響應交給自己編寫的解析方法做提取處理:1. 如果提取出需要的數據,則交給管道文件處理;2. 如果提取出url,則繼續執行之前的步驟(發送url請求,並由引擎將請求交給調度器入隊列…),直到請求隊列里沒有請求,程序結束。
17.談談你對Scrapy的理解?
Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架,我們只需要實現少量代碼,就能夠快速的抓取的數據內容。Scrapy使用了Twisted一部網絡框架來處理網絡通訊,可以加快我們的下載速度,不用自己去實現異步框架,並且包含了各種中間件接口,可以靈活的完成各種需求。
scrapy框架的工作流程:
- 首先Spider(爬蟲)將需要發送請求的url(requests)經ScrapyEngine(引擎)交給Scheduer(調度器)。
- Scheduler(排序,入隊)處理后,經ScrapyEngine(引擎),DownloaderMiddlewares(下載器中間件,可選,主要有User_Agent,Proxy代理)交給Downloader(下載器)。
- Downloader(下載器)向互聯網發送請求,並接受下載響應(response)。將響應(response)經ScrapyEngine(引擎),SpiderMiddlewares(爬蟲中間件,可選)交給Spiders(爬蟲)。
- Spiders(爬蟲)處理response(響應界面),提取數據並將數據經ScrapyEngineScrapyEngine(引擎)交給ItemPipeline保存(可以是本地,也可以是數據庫)。提取url重新經ScrapyEngineScrapyEngine(引擎)交給Scheduler(調度器)進入下一個循環。直到無Url請求程序停止結束。
優點: - scrapy是異步的;
- 采取可讀性更強的xpath代替正則;
- 強大的統計和log系統;
- 支持shell方式,方便獨立調試;
- 寫middleware,方便寫一些統一的過濾器;
- 通過管道的方式存入數據庫。
缺點: - 基於python的爬蟲框架,擴展性比較差;
- 基於 twisted 框架,運行中的 exception 是不會干掉 reactor(反應器),並且異步框架出錯后是不會停掉其他任務的,數據出錯后難以察覺
18.怎么樣讓 scrapy 框架發送一個 post 請求(具體寫出來)
19.怎么判斷網站是否更新?
1、304頁面http狀態碼
當第二次請求頁面訪問的時候,該頁面如果未更新,則會反饋一個304代碼,而搜索引擎也會利用這個304http狀態碼來進行判斷頁面是否更新。
首先第一次肯定是要爬取網頁的,假設是A.html,這個網頁存儲在磁盤上,相應地有個修改時間(也即是更新這個文件的時間)。
那么第二次爬取的時候,如果發現這個網頁本地已經有了,例如A.html,這個時候,你只需要向服務器發送一個If-Modified-Since的請求,把A.html的修改時間帶上去。
如果這段時間內,A.html更新了,也就是A.html過期了,服務器就會HTTP狀態碼200,並且把新的文件發送過來,這時候只要更新A.html即可。
如果這段時間內,A.html的內容沒有變,服務器就會返返回HTTP狀態碼304(不返回文件內容),這個時候就不需要更新文件。
2、Last-Modified文件最后修改時間
這是http頭部信息中的一個屬性,主要是記錄頁面最后一次的修改時間,往往我們會發現,一些權重很高的網站,及時頁面內容不更新,但是快照卻還是能夠每日更新,這其中就有Last-Modified的作用。通產情況下,下載網頁我們使用HTTP協議,向服務器發送HEAD請求,可以得到頁面的最后修改時間LastModifed,或者標簽ETag。將這兩個變量和上次下載記錄的值的比較就可以知道一個網頁是否跟新。這個策略對於靜態網頁是有效的。是對於絕大多數動態網頁如ASP,JSP來說,LastModifed就是服務器發送Response的時間,並非網頁的最后跟新時間,而Etag通常為空值。所以對於動態網頁使用LastModifed和Etag來判斷是不合適的,因此Last-Modified只是蜘蛛判斷頁面是否更新的一個參考值,而不是條件。
20.圖片、視頻爬取怎么繞過防盜連接
URL url = new URL("");
// 獲得連接
URLConnection connection = url.openConnection();
connection.setRequestProperty("Referer", "http://www.xxx.com");
因為一些網站在解決盜鏈問題時是根據Referer的值來判斷的,所以在請求頭上添加Referer屬性就好(可以填爬取網站的地址)。
另外Referer攜帶的數據 是用來告訴服務器當前請求是從哪個頁面請求過來的。
21.你爬出來的數據量大概有多大?大概多長時間爬一次?
22.用什么數據庫存爬下來的數據?部署是你做的嗎?怎么部署?
分布式爬蟲的部署
1.下載scrapy_redis模塊包
2.打開自己的爬蟲項目,找到settings文件,配置scrapy項目使用的調度器及過濾器
3.修改自己的爬蟲文件
4.如果連接的有遠程服務,例如MySQL,Redis等,需要將遠程服務連接開啟,保證在其他主機上能夠成功連接
5.配置遠程連接的MySQL及redis地址
6.上面的工作做完以后,開啟我們的redis服務器
進入到redis文件下打開我們的cmd窗口:輸入:redis-server redis.windows.conf
如果出現錯誤:# Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
解決方法:在命令行中運行
redis-cli
127.0.0.1:6379>shutdown
not connected>exit
然后重新運行redis-server redis.windows.conf,啟動成功!
7.修改redis.windows.conf配置文件,修改內容如下:
# 配置遠程IP地址,供其他的電腦進行連接redis
bind: (當前電腦IP) (192.168.40.217)
# 關閉redis保護模式
protected-mode: no
8.所有爬蟲都啟動之后,部署redis-server服務的電腦再打開一個命令窗口,輸入redis-cli.exe -h 127.0.0.1(如果是自己的ip改成自己的IP地址) -p 6379連上服務端
9.連上之后會有127.0.0.1:6379>這樣的字樣提示,然后輸入如下命令
10.lpush 爬蟲文件里面自己定義的爬蟲名字:start_urls 爬蟲的網址
12.數據寫不進去數據庫里面:
修改MySQL的my.ini文件,以MySQL8為例
路徑在C:\ProgramData\MySQL\MySQL Server 8.0
找到sql-mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"這一行
把里面的STRICT_TRANS_TABLES,刪除,逗號也刪除,保存文件
修改過之后需要重啟mysql服務
在windows命令窗口中使用net stop mysql80先停止服務,再使用net start mysql80啟動服務
如果my.ini文件不修改,爬蟲的數據寫入不了數據庫
23.增量爬取
①緩存
通過開啟緩存,將每個請求緩存至本地,下次爬取時,scrapy會優先從本地緩存中獲得response,這種模式下,再次請求已爬取的網頁不用從網絡中獲得響應,所以不受帶寬影響,對服務器也不會造成額外的壓力,但是無法獲取網頁變化的內容,速度也沒有第二種方式快,而且緩存的文件會占用比較大的內存,在setting.py的以下注釋用於設置緩存。這種方式比較適合內存比較大的主機使用。
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
②對item實現去重
#處理書信息
def process_BookItem(self,item):
bookItemDick = dict(item)
try:
self.bookColl.insert(bookItemDick)
print("插入小說《%s》的所有信息"%item["novel_Name"])
except Exception:
print("小說《%s》已經存在"%item["novel_Name"])
#處理每個章節
def process_ChapterItem(self,item):
try:
self.contentColl.insert(dict(item))
print('插入小說《%s》的章節"%s"'%(item['novel_Name'],item['chapter_Name']))
except Exception:
print("%s存在了,跳過"%item["chapter_Name"])
def process_item(self, item, spider):
'''
if isinstance(item,ChaptersItem):
self.process_ChaptersItem(item)
'''
if isinstance(item,BookItem):
self.process_BookItem(item)
if isinstance(item,ChapterItem):
self.process_ChapterItem(item)
return item
兩種方法判斷mongodb中是否存在已有的數據,一是先查詢后插入,二是先設置唯一索引或者主鍵再直接插入,由於mongodb的特點是插入塊,查詢慢,所以這里直接插入,需要將唯一信息設置為”_id”列,或者設置為唯一索引,在mongodb中設置方法如下
db.集合名.ensureIndex({"要設置索引的列名":1},{"unique":1})
需要用什么信息實現去重,就將什么信息設置為唯一索引即可(小說章節信息由於數據量比較大,用於查詢的列最好設置索引,要不然會非常慢),這種方法對於服務器的壓力太大,而且速度比較慢,我用的是第二種方法,即對已爬取的url進行去重
③對url實現去重
class UrlFilter(object):
#初始化過濾器(使用mongodb過濾)
def __init__(self):
self.settings = get_project_settings()
self.client = pymongo.MongoClient(
host = self.settings['MONGO_HOST'],
port = self.settings['MONGO_PORT'])
self.db = self.client[self.settings['MONGO_DB']]
self.bookColl = self.db[self.settings['MONGO_BOOK_COLL']]
#self.chapterColl = self.db[self.settings['MONGO_CHAPTER_COLL']]
self.contentColl = self.db[self.settings['MONGO_CONTENT_COLL']]
def process_request(self,request,spider):
if (self.bookColl.count({"novel_Url":request.url}) > 0) or (self.contentColl.count({"chapter_Url":request.url}) > 0):
return http.Response(url=request.url,body=None)
但是又會有一個問題,就是有可能下次開啟時,種子url已經被爬取過了,爬蟲會直接關閉,后來想到一個笨方法解決了這個問題,即在pipeline.py里的open_spider方法中再爬蟲開啟時刪除對種子url的緩存
def open_spider(self,spider):
self.bookColl.remove({"novel_Url":"http://www.23us.so/xiaoshuo/414.html"})
24.爬取下來的數據如何去重,說一下scrapy的具體的算法依據。
通過 MD5 生成電子指紋來判斷頁面是否改變
-
nutch 去重。nutch 中 digest 是對采集的每一個網頁內容的 32 位哈希值,如果兩個網頁內容完 全一樣,它們的 digest 值肯定會一樣。
-
數據量不大時,可以直接放在內存里面進行去重,python 可以使用 set()進行去重。當去重數據 需要持久化時可以使用 redis 的 set 數據結構。
-
當數據量再大一點時,可以用不同的加密算法先將長字符串壓縮成 16/32/40 個字符,再使用 上面兩種方法去重。
-
當數據量達到億(甚至十億、百億)數量級時,內存有限,必須用“位”來去重,才能夠滿足需 求。Bloomfilter 就是將去重對象映射到幾個內存“位”,通過幾個位的 0/1 值來判斷一個對象是 否已經存在。
-
然而 Bloomfilter 運行在一台機器的內存上,不方便持久化(機器 down 掉就什么都沒啦),也不 方便分布式爬蟲的統一去重。如果可以在 Redis 上申請內存進行
Bloomfilter,以上兩個問題就都能解 決了。 -
simhash 最牛逼的一點就是將一個文檔,最后轉換成一個 64 位的字節,暫且稱之為特征字,然后 判斷重復只需要判斷他們的特征字的距離是不是小於n(根據經驗這個 n 一般取值為 3),就可以判斷兩個 文檔是否相似。
-
可見 scrapy_redis 是利用 set 數據結構來去重的,去重的對象是 request 的 fingerprint(其實 就是用 hashlib.sha1()對 request 對象的某些字段信息進行壓縮)。其實 fp 就是 request 對象加密 壓縮后的一個字符串(40 個字符,0~f)。
# 去重源碼分析
# from scrapy.core.scheduler import Scheduler
# Scheduler下:def enqueue_request(self, request)方法判斷是否去重
if not request.dont_filter and self.df.request_seen(request):
Requests對象,RFPDupeFilter對象
# 如果要自己寫一個去重類
-寫一個類,繼承BaseDupeFilter類
-重寫def request_seen(self, request):
-在setting中配置:DUPEFILTER_CLASS = '項目名.dup.UrlFilter'
-增量爬取(100鏈接,150個鏈接)
-已經爬過的,放到某個位置(mysql,redis中:集合)
-如果用默認的,爬過的地址,放在內存中,只要項目一重啟,就沒了,它也不知道我爬過那個了,所以要自己重寫去重方案
-你寫的去重方案,占得內存空間更小
-bitmap方案
-BloomFilter布隆過濾器
from scrapy.http import Request
from scrapy.utils.request import request_fingerprint
# 這種網址是一個
requests1=Request(url='https://www.baidu.com?name=lqz&age=19')
requests2=Request(url='https://www.baidu.com?age=18&name=lqz')
ret1=request_fingerprint(requests1)
ret2=request_fingerprint(requests2)
print(ret1)
print(ret2)
# bitmap去重 一個小格表示一個連接地址 32個連接,一個比特位來存一個地址
# https://www.baidu.com?age=18&name=lqz ---》44
# https://www.baidu.com?age=19&name=lqz ---》89
# c2c73dfccf73bf175b903c82b06a31bc7831b545假設它占4個bytes,4*8=32個比特位
# 存一個地址,占32個比特位
# 10個地址,占320個比特位
#計算機計量單位
# 比特位:只能存0和1
def request_seen(self, request):
# 把request對象傳入request_fingerprint得到一個值:aefasdfeasd
# 把request對象,唯一生成一個字符串
fp = self.request_fingerprint(request)
#判斷fp,是否在集合中,在集合中,表示已經爬過,return True,他就不會再爬了
if fp in self.fingerprints:
return True
# 如果不在集合中,放到集合中
self.fingerprints.add(fp)
if self.file:
self.file.write(fp + os.linesep)
25.Scrapy的優缺點?
26.怎么設置爬取深度?
27.scrapy和scrapy-redis有什么區別?為什么選擇redis數據庫?
28.分布式爬蟲主要解決什么問題?
29.什么是分布式存儲?
傳統定義:分布式存儲系統是大量 PC 服務器通過 Internet 互聯,對外提供一個整體的服務。
分布式存儲系統具有以下的幾個特性:
可擴展 :分布式存儲系統可以擴展到幾百台甚至幾千台這樣的一個集群規模,系統的 整體性能線性增長。
低成本 :分布式存儲系統的自動容錯、自動負載均衡的特性,允許分布式存儲系統可 以構建在低成本的服務器上。另外,線性的擴展能力也使得增加、減少服務器的成本低, 實現分布式存儲系統的自動運維。
高性能 :無論是針對單台服務器,還是針對整個分布式的存儲集群,都要求分布式存 儲系統具備高性能。
易用 :分布式存儲系統需要對外提供方便易用的接口,另外,也需要具備完善的監 控、運維工具,並且可以方便的與其他的系統進行集成。
布式存儲系統的挑戰主要在於數據和狀態信息的持久化,要求在自動遷移、自動容 錯和並發讀寫的過程中,保證數據的一致性。
容錯:如何可以快速檢測到服務器故障,並自動的將在故障服務器上的數據進行遷移
負載均衡:新增的服務器如何在集群中保障負載均衡?數據遷移過程中如何保障不影 響現有的服務。
事務與並發控制:如何實現分布式事務。
易用性:如何設計對外接口,使得設計的系統易於使用。