python原生爬蟲+scrapy+redis分散式
資料獲取途徑
注意:robots.txt
1、瀏覽器版Chrome
2、手機版Chrome
3、合作網站(豬隊友網站)、子網站
請求方式
requests.get(url,headers = headers,verify=False,proxies = proxies);
requests.post(url,data=data,headers = headers,verify=False,proxies = proxies)
Requests: (第三方庫)
Response = requests.get(url,headers = headers)
Response = requests.post(url,data=data,headers = headers)
Response: (requests生成的例項物件)
Response.text 獲取str 資料
Response.content 獲取byte 資料 可以使用.decode()解碼
Response.status_code 獲取返回狀態碼(狀態碼200不一定代表資料獲取成功,僅代表請求成功)
Response.request.headers 獲取請求頭內容。
Response.headers 檢視請求的資料型別,(可以看到是json格式,utf-8編碼)
Proxies: 使用代理
Response = requests.get(url,headers = headers,proxies = proxies)
Prioxies形式:字典
Proxies = {
“http”:”http://123.123.123”,
“http”:”http://456.123.555”.
“http”:”http://123.977.123”.
................
}
處理cookies、session請求:
使用方法:
1、例項化一個session物件
2、讓session傳送get或者post請求
Session =requests.session()
Response = session.get(url,headers)
如果需要攜帶cookie訪問網站,需要獲取cookie。
三種方式:
1、需要登入,藉助session進行第一次訪問將
2、其他途徑獲取大量cookie,直接將cookie寫入請求data={}字典中
3、Get請求獲取cookie並寫入請求data中。
反爬機制
通常防止爬蟲被反主要有以下幾個策略:
1、動態設定User-Agent(隨機切換User-Agent,模擬不同使用者的瀏覽器資訊)
2、禁用Cookies(也就是不啟用cookies middleware,不向Server傳送cookies,有些網站通過cookie的使用發現爬蟲行為)
3、可以通過COOKIES_ENABLED 控制 CookiesMiddleware 開啟或關閉
4、Google Cache 和 Baidu Cache:如果可能的話,使用谷歌/百度等搜尋引擎伺服器頁面快取獲取頁面資料。
5、使用IP地址池:VPN和代理IP,現在大部分網站都是根據IP來ban的。
6、使用 Crawlera(專用於爬蟲的代理元件),正確配置和設定下載中介軟體後,專案所有的request都是通過crawlera發出。
7、設定延遲下載(防止訪問過於頻繁,設定為 2秒 或更高)
DOWNLOADER_MIDDLEWARES = {
'scrapy_crawlera.CrawleraMiddleware': 600
}
CRAWLERA_ENABLED = True
CRAWLERA_USER = '註冊/購買的UserKey'
CRAWLERA_PASS = '註冊/購買的Password'
使用代理前,先檢查代理ip可不可用。
使用requests模組檢查ip可用不可用,如果ip地址存放在redis中。使用程式不挺讀取ip地址,發起請求。判斷ip地址是否可用。如果不可用直接刪掉。在使用scrapy獲取Ip時,大部分都是可用的。
如果購買代理,需要使用到賬號密碼,使用bas64加密。參考html課件。
Url
建議:
如果url請求路徑有很多,無法確定真實的請求url路徑,可以嘗試提交內容的改變,檢視請求路徑中不變的Url. 參考可能需要的引數是否包含在From date中。增加判斷真確的機率。
1、from表單中的action=”www.xxx.com”可以獲得
Key、value是充from表單中的input標籤中獲取。
<input name=”email”>
<input name=”password”>
2、通過瀏覽器中Network中抓包
1. 通過輸入錯誤密碼,獲得請求路徑
2. 通過Chrome中勾選Preserve log功能,防止頁面跳轉找不到url,獲取請求路徑Name資訊。
備註:
通過pyquery的history屬性,發現baidu這些連線大多進行了redirect(http 302),但是有一部分連結是直接獲得了http200回覆。對於從百度爬取的加密的url,進行requests.get()時不允許跳轉(allow_redirects=False)。然後針對這兩類伺服器回覆分別處理:
http 302跳轉:從headers中的'location'可以獲得原始url;
http 200回覆:從content中通過正則表示式獲取原始url
3、火狐瀏覽器中編輯請求頭:
網路 → 訊息頭 → 可以編輯請求頭(增加或刪除referer等請求頭)重發。
資料來源的獲取
一 、如果獲取的資料是寫死的,不改變的。可以直接拿來用,不需要了解作用,
二 、如果獲取的資料是動態變換的,需要研究其生成方式,比如js方法
三 、如果傳遞相同的資料,得到的資料每次都是不同的,可以判定使用了【加鹽】,需要了解生成方式,使用Python程式碼完成相同邏輯。加密的兩種方式:伺服器儲存key、value,將鹽值傳送瀏覽器,邏輯加密後返回key、value;第二種相反。
1、通過Chremo中的Search all files進行全域性資料搜尋。
2、程式碼中打斷點,實現程式碼的邏輯資料顯示。
3、定位想要的js:
3.1 觸發事件:通過Chremo選擇會觸發的js事件的【按鈕】,Elements中Event Listeners ,Framework listeners功能鎖定js程式碼。
3.2 隱藏的傳輸事件:通過Chremo中Network中需要資料的Request URl 地址,通過路徑中的需要的引數(url關鍵字),尋找js程式碼。全域性搜尋Search all files。
4、在Sourses來源中檢視相應JS程式碼。
5、將未知的功能貼上至Console中,實現反轉。
Url路徑拼接
url路徑解碼:
XPATH 重點
一 、 在chrome瀏覽器中的Elements中檢視html進行除錯,但是爬蟲獲取的是url對應的響應,往往不同於elements中的格式。(應從實際已請求的返回值進行篩選,因為使用etree工具可能解析有誤,導致節點鎖定錯誤)需要從Network或者程式中使用etree.tostring列印修改後返回的html資料格式。
二 、 可以使用chremo中的xpath helper外掛提供的Copy→Copy XPath功能,直接生成選中節點的路徑。
獲取文字
a/text() 獲取a下的文字
a//text() 獲取a下的所有標籤的文字
//a[text()='下一頁'] 選擇文字為下一頁三個字的a標籤
@符號: 選取屬性
a/@href
//ul[@id="detail-list"]
/ 下一級(層)
// 下任何級(層)
在xpath最前面表示從當前html中任意位置開始選擇
li//a 表示的是li下任何一個標籤
li[2] li標籤第二個
li[last()-3] li標籤倒數第三個
ll[position()<4] li標籤前三個
| 或
//book/title | //book/price
1 、Contains 包含
//div[contains(@class, ”i” )] 表示所有div中包含class=”i”的標籤
2 、Not 不包含
特殊用法:
/book[price>35.00]/title 選取book元素中,price元素的值大於35.00的title元素
節點選擇法:
* 匹配任何元素節點
@* 匹配任何屬性節點
node() 匹配任何型別的節點
lxml能夠接受bytes和str的字串
提取頁面資料的思路:
先分組,取到一個包含分組標籤的列表 (先取物件,生成物件列表)
遍歷,取其中每一組進行資料的的提取,不會造成資料的對應錯亂。 (遍歷物件列表,針對每一層物件單獨寫獲取方式。配合三元表示式完成邏輯判斷)
Xpath的更多語法:https://msdn.microsoft.com/zh-cn/library/ms256039(v=vs.80).aspx
Queue佇列
程序會等所有的執行緒結束後再結束
設定守護執行緒,把當前的執行緒作為一個從屬的執行緒,依賴於另外一個執行緒,另外一個執行緒結束,當前執行緒就結束。
1、Queue.emty 判斷當前佇列是都是空的,如果是回覆True
2、Queue.full 判斷是都滿
3、Queue.get 從池中取資料
4、Queue.get_nowait 取資料,取不到就報錯
5、Queue.task_done() 對任務進行計數
6、Queue.put 推資料到池子中
7、Queue.put_nowait 推資料到池子,不成功報錯
8、Queue.qsize 獲取佇列儲存個數。
9、Queue.join 讓主執行緒堵塞(讓主執行緒等待佇列的任務數),等待子執行緒結束後主執行緒結束(python3中)
10、Thread.setDaemon(Ture) 讓該執行緒變成守護執行緒(該執行緒不重要,主執行緒結束,該執行緒結束)
11、
12、
注意:Queue.put 計數+1 ,Queue.get和Queue.task_done一起使用 計數-1
動態請求HTML(優化請求)
1 、 儘量減少請求次數(減少反扒機率)
能抓列表頁,不抓詳情頁。
儲存獲取到的html頁面,供插錯和重複請求使用
2 、 關注網站的所有型別的頁面(爬取難度:極速版頁面,手機版頁面,電腦版頁面)
Wap頁面,觸屏版頁面
H5頁面
APP
3 、 多偽裝(防止被反爬)
動態UA(headers={“User-Agent”:“http://.....”,。。。。}})
代理ip
不使用cookie
4 、 利用多執行緒分散式
在不被ban的請求下儘可能的提高速度
Selenium 和 PhantomJS
Selenium是 Web的自動化測試工具。 (效率比較低,訪問頁面需要載入完才能進行資料的獲取,速度慢。) 可以直接執行在瀏覽器上,支援所有主流瀏覽器(包括PhantomJS無邊界瀏覽器),可以接收命令,讓瀏覽器自動載入頁面,獲取需要的資料,甚至頁面截圖。
driver = webdriver.Chrome() # 例項化一個瀏覽器 (Chrome需要安裝)
driver.get(‘http://www.baidu.com’) # 傳送請求
time.sleep(3) # 睡3秒
driver.quit() # 退出瀏覽器
driver.get_cookies() # 獲取cookies 列表
列表推導式: cookies = {i["name"]:i["value"] for i in cookies_list}
driver.page_source # 瀏覽器中elements的內容,瀏覽器所有內容載入完畢後的html字串
driver.current_url # 頁面載入/跳轉完成後的url地址。
driver.close() # 退出當前頁面
元素定位的方法:
find_element_by_id (根據id返回一個)
find_elements_by_xpath (返回列表,如果路徑寫錯,獲取不到返回空列表 [] )
find_elements_by_link_text (根據連線的文字選擇)
find_elements_by_partial_link_text (根據連線的部分文字選擇)
find_elements_by_tag_name (根據標籤選擇)
find_elements_by_class_name (根據class選擇)
find_elements_by_css_selector (根據CSS選擇)
獲取文字和獲取屬性
先定位到元素,然後呼叫.text或者get_attribute方法來去
selenium獲取的頁面資料是瀏覽器中elements的內容
findelement和findelements的區別
find_element返回一個element,如果沒有會報錯
find_elements返回一個列表,沒有就是空列表
在判斷是否有下一頁的時候,使用find_elements來根據結果的列表長度來判斷
如果頁面中含有iframe、frame,需要先呼叫driver.switch_to.frame的方法切換到frame中才能定位元素
<iframe id = “aaa” name = “bbb” ..............>
driver.switch_to.frame(“aaa”)
或driver.switch_to.frame(“bbb”)
或driver.switch_to.frame(Selenium選擇的元素)
細節重點:
selenium請求第一頁的時候回等待頁面載入完了之後在獲取資料,但是在點選翻頁之後,hi直接獲取資料,此時可能會報錯,因為資料還沒有加載出來,需要time.sleep(3)
selenium中find_elements_by_class_name智慧接收一個class對應的一個值,不能傳入多個
driver.find_element_by_id(“kw”).send_key(“python”) # 選中input框,輸入內容“python”
driver.find_element_by_id(“su”).click() # 選中input框,執行點選事件。
PhantomJS 是 無介面(headless)瀏覽器 , 它將網站載入到記憶體並執行頁面JavaScript。
driver = webdriver.PhantomJS() # 例項化記憶體瀏覽器 (PhantomJS需要安裝)
driver.set_window_size() # 設定截圖大小 如(1980,1080)
driver.maximize_window() # 最大化視窗
driver.save_screenshot(“./name.png”) 儲存截圖圖片位置、型別
Cookie相關用法
{cookie[‘name’]:cookie[‘value’] for cookie in driver.get_cookies()} 列表推導式
driver.delete_cookie(“CookieName”) 刪除cookie
driver.delete_all_cookies()
應用場景:
1、cookie過期時間很長,常見於一些不規範的網站
2、能在cookie過期之前把搜有的資料拿到
3、配合其他程式使用,比如其使用selenium把登陸之後的cookie獲取到儲存到本地,scrapy傳送請求之前先讀取本地cookie
注意:
1、在scrapy框架中,setting中預設cookis在下一次請求中預設攜帶的。如果第一次請求中獲得,或者攜帶cookie後面的請求就會攜帶從傳遞。
2、Scrapy中攜帶cookies請求不能寫到headers中。原生的requests模組可以將cookies寫到headers中。
驗證碼
1、驗證碼的識別
url不變,驗證碼不變
2、請求驗證碼的地址,獲得相應,識別
url不變,驗證碼會變
使用Selenium工具。
使用截圖工具PIL 模組,可以實現截圖。注意,需要將瀏覽器寬高固定,要不然截圖位置無法定位。
思路:對方伺服器返回驗證碼的時候,會和每個使用者的資訊和驗證碼進行一個對應,之後,在使用者傳送post請求的時候,會對比post請求中法的驗證碼和當前使用者真正的儲存在伺服器端的驗證碼是否相同
驗證碼的獲取需要當前使用者的cookie,所以需要session發驗證碼請求,寫入session中一個cookie;
如果使用requests傳送請求,需要攜帶cookie值,並將cookie值在不同的請求間傳遞使用。
1. 例項化session
2.使用seesion請求登入頁面,獲取驗證碼的地址
3.使用session請求驗證碼,識別
4.使用session傳送post請求’
使用selenium登入,遇到驗證碼
url不變,驗證碼不變,同上
url不變,驗證碼會變
1.selenium請求登入頁面,同時拿到驗證碼的地址
2.獲取登入頁面中driver中的cookie,交給requests模組傳送驗證碼的請求,識別
3.輸入驗證碼,點選登入
賬號:183************
密碼:******
下載相關執行包:
# 使用者名稱
username = '183********'
# 密碼
password = '**********'
# appid
appid = 4283
# appkey
appkey = '02074c64f0d0bb9efb2df455537b01c3'
# 驗證碼型別
codetype = 3007
# 超時
timeout = 60
注意點:
1、獲取html中的標籤路徑時,elements中的<tbody>標籤並不存在。
2、可以使用 切片 [1:-1]來過濾獲取的list。
3、From表單提交中,完整內容應該包還有:
使用者名稱、密碼
csrf_token
action method post enctype=application/www-x-urlencoded
authenticity_token
資料的格式整理
1、通過Chremo瀏覽器中Sources中的{} :Pretty print功能實現格式的轉換
2、Chremo中安裝JSONView外掛。
3、網頁線上
4、Pychem
5、電腦安裝Atom
1、json.dumps ; json.loads ; json.dump ; json.load ; str() ; eavl() 用法區別:
json.dumps()
Python資料型別 → json字串
將dict型別的資料轉成str
json.loads()
json字串 → Python資料型別
json.loads()用於將str型別的資料轉成dict。
注:具有read()或者write()方法的物件就是類檔案物件
f=open(“a.txt”,”r”) f就是類檔案物件
json.dump()
python資料型別 → 包含json的類檔案物件
json.dump()用於將dict型別的資料轉成str,並寫入到json檔案中。
json.load()
包含json的類檔案物件 → python資料型別
json.load()用於從json檔案中讀取資料。
Str()
雖然可以轉換為字串,但是在write寫入過程中,沒有json.dumps靈活,例如:f.write(json.dumps(data,ensure_ascii=False,indent=2))可以攜帶格式引數。
Eavl()
處理不了層級太多的程式碼,容易出錯。
2、lxml庫
lxml可以自動修正/補全html程式碼(如果傳遞的資料標籤有缺失/不規範,可能導致修正後格式有誤。)
利用etree.HTML 將字串(str/byte型別)轉化為Element物件
Element物件具有xpath的方法
from lxml import etree
html = etree.HTML(text) # todo 返回的是html物件
etree.tostring( html ) # todo 獲取element物件中包含的byte型別的字串
html.xpath(“語句”) # todo 獲取的是個列表集合(根據獲取路徑可以是:物件、herf屬性/text文字)
3、Print( etree.tostring(html物件).decode() )
檢視通過etree修正後的html格式。
Scrapy shell互動終端
我們可以在未啟動spider的情況下嘗試及除錯程式碼,也可以用來測試XPath表示式
使用方法:
1、進入ipython/python互動終端
2、scrapy shell http://www.itcast.cn/channel/teacher.shtml
response.url:當前響應的url地址
response.request.url:當前響應對應的請求的url地址
response.headers:響應頭
response.body:響應體,也就是html程式碼,預設是byte型別
response.requests.headers:當前響應的請求頭
注意:response.headers會採用預設的請求頭,所以需要先設定User-Agent:
3、自動拼接不完整的url路徑:urljoin()
import urllib
a = “aaa/aaa/aa/aaa/aa/aaa/ccc/ccc/cc/c/cccc”
b = “bbb/bbbb/bbbb/bbbb/bbb”
urllib.parse.urljoin(a,b)
→”aaaa/aaa/aaa/aaa/bbb/bbb/bbb/bbb/bbb/bbb”
資料的清洗
1、Response =requests.get(“http://www.baidu.com”)
Get請求獲取響應
2、response.cookies
返回值中可以提取cookies
3、requests.utils.dict_from_cookiejar(response.cookies)
Cookie轉字典
4、Requests.utils.cookiejar_from_dict({“key”:”value”})
字典轉換cookie
5、url地址解碼:
Requests.utils.unquote(“http%2f%2f..............”)
→“http://...............”
6、url地址編碼:
Repuest.utils.quote(“http://........”)
→”%ad123%232%sdfds.........”
7、SSLError:
SSL證書問題,可能存在安全隱患。(Https)
解決方法:新增verify=False response = requests.get(“http://www.baidu.com”, verify = False)
8、設定請求時間限制引數
timeout = 10 response = requests.get(url , timeout = 10)
9、Eval
eval() 能實現簡單的字串和Python型別的轉換
10、Replace
把單引號替換為雙引號
11、re.compile(編譯)
Compile將字串按照傳入的正則方法,預先匹配一遍,當呼叫compile物件的方法時,提高效率。
注意:如果使用compile() , re.S需要放到compile(“.” , re.S)中
例如:
b = “asdasd123”
p = re.compile(“\d”)
p.findall(b)
→ [“1”,”2”,”3”]
12、match(從頭找一個)
13、findall(找所有)
Re.findall(“.” , “\n”, re.DOTALL)
在DOTALL模式中可以匹配換行符。re.DOTALL也可以寫成re.S
14、sub(替換)
re.sub(“\d”,”_”,b) 從b中匹配第一個條件”\d”,為”_”。
15、遞迴函式:
16、列表推導式
content = [re.sub(r"\xa0|\s","",i) for i in content]
content = [i for i in content if len(i)>0] #去除列表中的空字串
17、[ 0 : -1]
18、注意資料的覆蓋。Item結構(item的深拷貝)
Item如果設定的傳遞層數太多,再寫入資料庫中,會因為底層的函式欄位相同而覆蓋。所以傳遞item中,在適當節點需要使用硬拷貝deepcopy()函式。deepcopy(item)進行引數物件的傳遞,保證資料不會覆蓋。
19、隨機數random用法:
random.random()函式是這個模組中最常用的方法了,它會生成一個隨機的浮點數,範圍是在0.0~1.0之間。
random.uniform()正好彌補了上面函式的不足,它可以設定浮點數的範圍,一個是上限,一個是下限。
random.randint()隨機生一個整數int型別,可以指定這個整數的範圍,同樣有上限和下限值,python random.randint。
random.choice()可以從任何序列,比如list列表中,選取一個隨機的元素返回,可以用於字串、列表、元組等。
random.shuffle()如果你想將一個序