基本框架參考 5 使用ip代理池爬取糗事百科html
其中,加載網頁使用的方式:python
def load_page(self, url, header): print ("進入load_page函數") print("load_url:",url) #獲取有效能使用的代理 proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) while ((proxy==None)|(success==False)): proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) continue print("獲取有效能使用的代理是:",proxy) proxy=proxy.decode('utf-8')#必須解碼。若是直接轉爲str,前面會加上b',變成相似b'101.96.11.39:86的形式 print (proxy) proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) #參數傳入"http://"+str(proxy) headers=("User-Agent",header) opener=urllib.request.build_opener(proxy) opener.addheaders=[headers] try: response=opener.open(url) data=response.read() except HTTPError as e: print(("訪問%s出現HTTP異常")%(url)) print(e.code) print(e.reason) return None except URLError as e: print(("訪問%s出現URL異常")%(url)) print(e.reason) return None finally: pass #read返回的是bytes。 print ("使用代理成功加載url:",url) print ("退出load_page函數") return data #使用代理加載網頁
編碼網址的方式:nginx
key="博士怎麼讀" pagestart=1 pageend=10 for page in range(pagestart,pageend+1): #若是超出糗事百科熱門內容的頁數,均會被導向第一頁。 self.pagenum=page #編碼"&page" header = self.headers() urltmp="http://weixin.sogou.com/weixin?type=2&query="+key+"&page="+str(page) url=urllib.request.quote(urltmp,safe=";/?:@&=+$,",encoding="utf-8") #safe是指定安全字符 html = self.load_page(url, header) self.parse(html,3)
結果報出:數據庫
http.client.InvalidURL: nonnumeric port: '60088''flask
60088就是當時所用代理的端口號瀏覽器
nonnumeric port: '60088''的解決
我訪問糗事百科的網址,也用的是這些代理,就沒有這麼多問題。安全
爲何這裏編碼了微信搜索平臺的網址之後,仍是不行呢。服務器
https://stackoverflow.com/questions/16253049/python-nonnumeric-port-exception微信
裏面建議用:app
使用pycurl替代urllib
https://blog.csdn.net/xsj_blog/article/details/52102652
https://blog.csdn.net/sunchenzl/article/details/50373689
http://pycurl.io/docs/latest/quickstart.html官網
#使用pycurl c=pycurl.Curl() c.setopt(pycurl.URL,url) #模擬瀏覽器 c.setopt(pycurl.USERAGENT,header) #使用代理 proxy_str="http://"+str(proxy) print (proxy_str) c.setopt(pycurl.PROXY,proxy_str) b = io.StringIO() #StringIO仍然是獲取bytes流 c.setopt(pycurl.WRITEFUNCTION, b.write) c.perform() data=b.getvalue() return data
可是報出:
error: (5, "Could not resolve proxy: b'59.106.213.54")
而輸出proxy_str的時候,發現代理地址變成了這個。
pycurl和urllib使用代理的不一樣,建議一概加上proxy=proxy.decode('utf-8')語句
urllib使用代理:
# proxy=urllib.request.ProxyHandler({"http":"http://"+str(proxy)})
# headers=("User-Agent",header)
其中,str(proxy)轉化之後,會多帶一個b’, 相似於
http://b'203.130.46.108:9090'就沒有任何問題。
可是pycurl就會出問題。
可能urllib.request.ProxyHandler內部有更嚴謹的處理機制。
從開源代理池項目中獲取的proxy,見 5 使用ip代理池爬取糗事百科
也就是從flask接口獲取的時候,可能因爲編碼問題。數據庫中的101.96.11.39:86在獲取到的時候,會是一個bytes對象。
這個bytes對象,若是使用str或者.format的形式直接轉變爲字符串,或者拼接到字符串後面的時候,會出現一個重大的bug。也就是,會變成b’ 101.96.11.39:86’。這個時候,很巧的是:
proxy=urllib.request.ProxyHandler({"http":"http://"+str(proxy)})比較變態,可以正確處理。
可是,pycurl則不能正常處理。
pycurl的問題
- l ipdb> pycurl.error: (7, 'Failed to connect to 60.13.156.45 port 8060: Timed out')
代理鏈接不上。Pycurl連不上代理。可是urllib能鏈接上。個人每個代理都是驗證可以使用之後才用的。驗證的方式是用urllib進行驗證。而這裏pycurl卻不能用。
- l 從新運行程序,從數據庫中又取出了一個代理,結果再也不提示代理錯誤,又出現
ipdb> pycurl.error: (23, 'Failed writing body (0 != 154)')
按網上的說法講stringio改成io.BytesIO便可。 |
可是又出現新的錯誤,即ipdb> pycurl.error: (52, 'Empty reply from server')
改成post方式:
c=pycurl.Curl() url="http://weixin.sogou.com/weixin" c.setopt(pycurl.URL,url) #模擬瀏覽器 c.setopt(pycurl.USERAGENT,header) #使用代理 proxy=proxy.decode('utf-8') #避免變成b'203.130.46.108:9090',這是開源代理池項目的弊端 print (proxy) proxy_str="http://{}".format(proxy) print (proxy_str) post_data_dic = urllib.parse.urlencode({ "type":"2", "key":"博士怎麼讀", "page":"1" }).encode('utf-8') c.setopt(pycurl.POSTFIELDS,post_data_dic) c.setopt(pycurl.PROXY,proxy_str) b = io.BytesIO() c.setopt(pycurl.WRITEFUNCTION, b.write) c.perform() data=b.getvalue() print (data) return data
結果輸出的是:
b'<html>\r\n<head><title>302 Found</title></head>\r\n<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n'
可見pycurl不能解決。
繼續用urllib解決問題(終極解決方案,誤打誤撞)
在前面使用pycurl之後,我準備使用post方式去解決這個問題。因而回到urllib之後,我改用post方式去解決這個問題。因爲寫程序的時候,post方式必然要求以下寫法:
proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) while ((proxy==None)|(success==False)): proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) continue print("獲取有效能使用的代理是:",proxy) proxy=proxy.decode('utf-8')#必須解碼。若是直接轉爲str,前面會加上b',變成相似b'101.96.11.39:86’的字符串 print (proxy) proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) req=urllib.request.Request(url) req=urllib.request.Request(url,postdata) #req.add_header("User-Agent",header) req.set_proxy("http://{}".format(proxy),"http") try: response=urllib.request.urlopen(req) data=response.read() except HTTPError as e: print(("訪問%s出現HTTP異常")%(url)) print(e.code) print(e.reason) return None except URLError as e: print(("訪問%s出現URL異常")%(url)) print(e.reason) return None except Exception as e: print(("訪問%s出現異常")%(url)) print(e) return None finally: pass #read返回的是bytes。 print ("使用代理成功加載url:",url) print ("退出load_page函數") return data
接着傳入postdata。
postdata=urllib.parse.urlencode({
"type":type,
"key":key,
"page":page
}).encode('utf-8')
這個時候個人url忘記改了:仍然是:
urltmp="http://weixin.sogou.com/weixin?type=2&query="+key+"&page="+str(page) url=urllib.request.quote(urltmp,safe=";/?:@&=+$,",encoding="utf-8")
- 網頁解析成功。我覺得是post方式起了做用。當我將url改成http://weixin.sogou.com之後,程序馬上報出異常,即訪問http://weixin.sogou.com/出現HTTP異常,405Not Allowed
- 這個時候,我將上面的程序中的
req=urllib.request.Request(url,postdata)
改成:
req=urllib.request.Request(url)
問題馬上解決了。
這說明什麼:http://weixin.sogou.com沒法處理post數據。也就是服務器就不接受post請求。
最後的解決措施是什麼:
從:
proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) #參數傳入"http://"+str(proxy) headers=("User-Agent",header) opener=urllib.request.build_opener(proxy) opener.addheaders=[headers] try: response=opener.open(url) data=response.read()
改成:
proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) req=urllib.request.Request(url) req=urllib.request.Request(url,postdata) #req.add_header("User-Agent",header) req.set_proxy("http://{}".format(proxy),"http") try: response=urllib.request.urlopen(req) data=response.read()
就是這麼一點簡單的變更:下面的url編碼之後可以直接在瀏覽器中打開
urltmp="http://weixin.sogou.com/weixin?type=2&query="+key+"&page="+str(page) url=urllib.request.quote(urltmp,safe=";/?:@&=+$,",encoding="utf-8")
處理一樣的url編碼:
前者會提示http.client.InvalidURL: nonnumeric port: '60088'''60088''就是你用的代理的端口號。
這說明,urllib.request.urlopen要比opener.open使用起來更健壯。
這必定是urllib的一個bug。由於前者在以前作糗事百科爬蟲的時候也會考慮到page的變更,就不會出現這個問題。作微信搜索平臺爬蟲只多了一個key=「博士怎麼讀」,可是編碼的也沒有任何問題,在瀏覽器中可以打開編碼後的網址。可是就是會報出異常。
完整代碼
# -*- coding: utf-8 -*- """ Created on Sat Jul 14 15:24:51 2018 @author: a """ import urllib.request import re from urllib.error import HTTPError from urllib.error import URLError import os import time import random from lxml import etree import requests import sys import pycurl import io from threading import Thread sys.path.append('H:\proxy_pool-master') from Util.utilFunction import validUsefulProxy class MySpider: pagenum=0 def headers(self): headers_list = [ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5193.400 QQBrowser/10.0.1066.400", "Mozilla/5.0(compatible;MSIE9.0;WindowsNT6.1;Trident/5.0", "Mozilla/4.0(compatible;MSIE8.0;WindowsNT6.0;Trident/4.0)", "Mozilla/4.0(compatible;MSIE7.0;WindowsNT6.0)", "Mozilla/5.0(WindowsNT6.1;rv:2.0.1)Gecko/20100101Firefox/4.0.1", "Opera/9.80(WindowsNT6.1;U;en)Presto/2.8.131Version/11.11", "Mozilla/4.0(compatible;MSIE7.0;WindowsNT5.1;TencentTraveler4.0)", "Mozilla/4.0(compatible;MSIE7.0;WindowsNT5.1;Maxthon2.0)", "Mozilla/4.0(compatible;MSIE7.0;WindowsNT5.1;360SE)", "Mozilla/4.0(compatible;MSIE7.0;WindowsNT5.1)", ] ua_agent = random.choice(headers_list) return ua_agent def load_page(self, url, header,postdata=None): print ("進入load_page函數") print("load_url:",url) #獲取有效能使用的代理 proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) while ((proxy==None)|(success==False)): proxy=self.get_proxy() print("暫取出的代理是:",proxy) success=validUsefulProxy(proxy) print("代理是否有效的驗證結果:",success) continue print("獲取有效能使用的代理是:",proxy) proxy=proxy.decode('utf-8')#必須解碼。若是直接轉爲str,前面會加上b',變成相似b'101.96.11.39:86的形式 print (proxy) proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) req=urllib.request.Request(url) ##不能用postdata的方式,由於http://weixin.sogou.com不處理post數據 #req=urllib.request.Request(url,postdata) req.add_header("User-Agent",header) req.set_proxy("http://{}".format(proxy),"http") try: response=urllib.request.urlopen(req) data=response.read() except HTTPError as e: print(("訪問%s出現HTTP異常")%(url)) print(e.code) print(e.reason) return None except URLError as e: print(("訪問%s出現URL異常")%(url)) print(e.reason) return None except Exception as e: print(("訪問%s出現異常")%(url)) print(e) return None finally: pass #read返回的是bytes。 print ("使用代理成功加載url:",url) print ("退出load_page函數") return data #用urllib解析網址,老是報出nonnumeric port異常,並且異常數字是代理的端口號。 #以前搜索糗事百科的時候就沒有這個問題。並且我編碼後的url直接在瀏覽器中打開,就能 #訪問微信搜索平臺。所以,用post方式試試。 # proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) # headers=("User-Agent",header) # opener=urllib.request.build_opener(proxy) # opener.addheaders=[headers] # try: # response=opener.open(url) # data=response.read() # except HTTPError as e: # print(("訪問%s出現HTTP異常")%(url)) # print(e.code) # print(e.reason) # return None # except URLError as e: # print(("訪問%s出現URL異常")%(url)) # print(e.reason) # return None # except Exception as e: # print(("訪問%s出現異常")%(url)) # print(e) # return None # finally: # pass # #read返回的是bytes。 # print ("使用代理成功加載url:",url) # print ("退出load_page函數") # return data #使用代理加載網頁 def parse(self, html,switch): print ("進入parse函數") if switch==3: print("這裏是微信搜索平臺的網頁解析規則") data=html.decode('utf-8') #print (data) xpath_value='//div[@class="txt-box"]/h3/a/@href' #注意xpath選擇器返回的是列表 selector = etree.HTML(data) titilelist=selector.xpath(xpath_value) print(titilelist) print ("解析第{}頁,共獲取到{}篇文章".format(self.pagenum,len(titilelist))) else: print("還沒有制定解析規則") print ("退出parse函數") def get_proxy(self): return requests.get("http://localhost:5010/get/").content def delete_proxy(self,proxy): requests.get("http://localhost:5010/delete/?proxy={}".format(proxy)) def main(self): #設置搜索關鍵詞 key="博士怎麼讀" pagestart=1 pageend=10 for page in range(pagestart,pageend+1): #若是超出糗事百科熱門內容的頁數,均會被導向第一頁。 self.pagenum=page #編碼"&page" ##不能用postdata的方式,由於http://weixin.sogou.com不處理post數據 # postdata=urllib.parse.urlencode({ # "type":str(type__), # "key":key, # "page":str(page) # }).encode('utf-8') header = self.headers() urltmp="http://weixin.sogou.com/weixin?type=2&query="+key+"&page="+str(page) url=urllib.request.quote(urltmp,safe=";/?:@&=+$,",encoding="utf-8") #safe是指定安全字符 html = self.load_page(url, header) self.parse(html,3) if __name__ == "__main__": myspider = MySpider() myspider.main()
反思
程序調試其實對我的的水平是沒有什麼進步的。老是在向着錯誤的方向去排查問題。
只能但願之後少遇到這種問題。先思考幾點問題:
- 正確的作法是什麼?書上其實用的就是urllopen,可是我平時用opener.open習慣了,造成了一種慣性。因而,我老是懷疑是url編碼出問題,或者根據錯誤的代碼去搜索解決方案。而忽略了一件事情,那就是,正確的作法是什麼。若是跟書中的代碼進行了比對,馬上就解決了。我平時看代碼很快,可是沒想到老是把大量時間花在了調試上。
- 其次,適當的時候,學會尋求別人的幫助。時間是無價之寶,不要耗費在這種細節和小問題上,不然,時間沒了,就是真的沒了。
2018年7月20日20:28:55日補充:nonnumerical port錯誤真正的緣由
後來上面的代碼再次遇到錯誤:說//是非數字端口號,即nonnumerical port。
真正的錯誤是:
- 我一開始用的是:proxy=urllib.request.ProxyHandler({"http":"{}".format(proxy)})。接着又req.set_proxy("{}".format(proxy),"http")。所以存在了重複。此時proxy已經不是相似「101.96.11.58:80」的內容了。
- 此外設置代理的時候,不要用:#proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)})而應該是:proxy=urllib.request.ProxyHandler({"http":"{}".format(proxy)})。這也是爲何會提示://非數字端口號的緣由
將最初使用的open的代碼
proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) #參數傳入"http://"+str(proxy) headers=("User-Agent",header) opener=urllib.request.build_opener(proxy) opener.addheaders=[headers] try: response=opener.open(url) data=response.read()
改成:
proxy=urllib.request.ProxyHandler({"http":"http://{}".format(proxy)}) #參數傳入"http://"+str(proxy) headers=("User-Agent",header) opener=urllib.request.build_opener(proxy) opener.addheaders=[headers] try: response=opener.open(url) data=response.read()
問題也解決了。
因此,根本緣由在於設置的時候用的是相似於下面的,而書上是不須要在value值中放入http://的。
({"http":"http://{}".format(proxy)})