6 爬取微信搜索平台的微信文章保存為本地網頁


基本框架參考 5 使用ip代理池爬取糗事百科

其中,加載網頁使用的方式:

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
        #使用代理加載網頁   

編碼網址的方式:

        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''

60088就是當時所用代理的端口號

nonnumeric port: '60088''的解決

我訪問糗事百科的網址,也用的是這些代理,就沒有這么多問題。

為什么這里編碼了微信搜索平台的網址以后,還是不行呢。

https://stackoverflow.com/questions/16253049/python-nonnumeric-port-exception

里面建議用:

使用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的時候,發現代理地址變成了這個。

http://b'203.130.46.108:9090'

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)}) 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM