1. 程式人生 > 實用技巧 >爬蟲中常見問題

爬蟲中常見問題

1、爬取內容顯示亂碼

1、原因:比如網頁編碼是gbk編碼的,但是我們用了錯誤的方式比如utf-8解碼,因而出現亂碼
2、基礎知識:
    (1)python3.6 預設編碼為Unicode;正常的字串就是Unicode
    (2)計算機中儲存的資訊都是二進位制的
    (3)編碼decode:真實字元→二進位制
    (4)解碼encode:二進位制→真實字元
    (5)一般來說在Unicode2個位元組的,在UTF8需要3個位元組;但對於大多數語言來說,只需要1個位元組就能編碼,如果採用Unicode會極大浪費,於是出現了變長的編碼格式UTF8
    (
6)GB2312的出現,基本滿足了漢字的計算機處理需要,但對於人名、古漢語等方面出現的罕用字,GB2312不能處理,這導致了後來GBK及GB18030漢字字符集的出現。 3、各種編碼方式: (1)ASCII :1位元組8個bit位表示一個字元的編碼格式,最多可以給256個字(包括字母、數字、標點符號、控制字元及其他符號)分配(或指定)數值。 (2)ISO8859-1 :1位元組8個bit位表示一個字元的編碼格式,僅支援英文字元、數字及常見的符號,3.6%的全球網站使用這個編碼。 (3)GB2312:2位元組16個bit位表示一個字元的編碼格式,基本滿足了漢字的計算機處理需要 (
4)GBK:2位元組16個bit位表示一個字元的編碼格式,GBK即漢字內碼擴充套件規範 (5)Unicode :2位元組16個bit位表示一個字元的編碼格式,基本能把全球所有字元表示完, (6)UTF8:變長位元組編碼格式,英文1位元組,漢字3位元組,較複雜的更高位元組編碼, 4、例項: s = "" #預設Unicode編碼 print(s.encode("gbk")) #Unicode轉gbk #輸出2位元組: b'\xba\xc3' print(s.encode("utf-8")) #Unicode轉utf-8 #輸出3位元組:b'\xe5\xa5\xbd' print
(s.encode("gb2312")) #Unicode轉gb2312 #輸出2位元組:b'\xba\xc3' print(s.encode("gb2312").decode("gb2312")) #Unicode解碼為gb2312再編碼為Unicode print(s.encode("utf8").decode("utf8")) #Unicode解碼為utf8再編碼為Unicode #輸出:好

(2)解決方法

方法:
    檢視網頁是什麼編碼,並設定該編碼格式,或者包含大於這個的編碼,如gb2312編碼的網頁可以設定gbk的編碼方式。
程式碼:
    solution1:response.encoding = response.apparent_encoding
    solution2 :response.encoding = 'utf-8'
                response.encoding = 'gbk'

2、pymongo.errors.CursorNotFound:

(1)原因:

預設 mongo server維護連線的時間視窗是十分鐘;預設單次從server獲取資料是101條或者 大於1M小於16M的資料;所以預設情況下,如果10分鐘內未能處理完資料,則丟擲該異常。

(2)解決方法:

方法:
    no_cursor_timeout=True:設定連線永遠不超時
    batch_size:估計每批次獲取資料量的條數;讓MongoDB客戶端每次抓取的文件在10分鐘內能用完
程式碼:
    import pymongo
    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.test
    collection = db.testtable
    cursor = collection.find(no_cursor_timeout=True, batch_size=5)

3、TypeError: can’t pickle _thread.lock objects和EOFError: Ran out of input

(1)原因:

1、程序池內部處理使用了pickle模組(用於python特有的型別和python的資料型別間進行轉換)中的dump(obj, file, protocol=None,)方法對引數進行了封裝處理.
2、在引數傳遞中如果自定義了資料庫儲存類mongo或者redis等資料庫,會造成程序池內部處理封裝過程無法對其進行處理. 
3、錯誤程式碼產生異常的例項1:
    import multiprocessing
    import pymongo
    class Test:
        def __init__(self, collection):
            self.collection = collection
        def savedata(self):
            self.collection.insert_one({'key': 'value'})
    def main():
        client = pymongo.MongoClient(host='localhost', port=27017)
        db = client.test
        collecttable = db.testtable
        test = Test(collecttable)
        p1 = multiprocessing.Process(target=test.savedata)
        p1.start()
        p1.join()
        print('程序已結束')
    if __name__ == '__main__':
        main()
4、錯誤程式碼產生異常的例項2:
    import multiprocessing
    import pymongo
    class Test:
        def __init__(self):
            pass
        def savedata(self, collecttable):
            collecttable.insert_one({'key': 'value'})
    def main():
        client = pymongo.MongoClient(host='localhost', port=27017)
        db = client.test
        collecttable = db.testtable
        test = Test()
        p1 = multiprocessing.Process(target=test.savedata, args=(collecttable,))
        p1.start()
        p1.join()
        print('程序已結束')
    if __name__ == '__main__':
        main()

(2)解決方法:

方法:
    在引數傳遞時,不能將資料庫集合作為類的引數進行傳遞,只能在函式裡面建立使用資料庫
程式碼:
    import multiprocessing
    import pymongo
    class Test:
        def __init__(self):
            pass
        def savedata(self):
            client = pymongo.MongoClient(host='localhost', port=27017)
            db = client.test
            collecttable = db.testtable
            collecttable.insert_one({'key': 'value'})
    def main():
        test = Test()
        p1 = multiprocessing.Process(target=test.savedata)
        p1.start()
        p1.join()
        print('程序已結束')
    if __name__ == '__main__':
        main()

4、redis.exceptions.DataError: Invalid input of type: ‘dict’. Convert to a byte, string or number first.

(1)原因:

1、redis存入資料型別錯誤,應該是位元組或者是字串或者是數字型別
2、錯誤例項:
    from redis import StrictRedis
    dict = {'key': 'value'}
    r = StrictRedis(host='localhost', port=6379)
    r.rpush('test', dict)

(2)解決方法:

方法:
    使用json模組,json.dumps(dict)可以將字典型別的轉換為字串
程式碼:
    import json
    from redis import StrictRedis
    dict = {'key': 'value'}
    r = StrictRedis(host='localhost', port=6379)
    data = json.dumps(dict)
    r.rpush('test', data)
    print(r.lpop('test'))
    #輸出: b'{"key": "value"}'

5、json.dumps()中文未正確顯示

(1)原因:

1、json.dumps序列化時對中文預設使用的ascii編碼.想輸出真正的中文需要指定ensure_ascii=False:
2、例項程式碼:
    import json
    dict = {'key': '測試'}
    print(json.dumps(dict))
    # 輸出:{"key": "\u6d4b\u8bd5"}

(2)解決方法:

方法:
    json.dumps(dict,ensure_ascii = False)
程式碼:
    import json
    dict = {'key': '測試'}
    print(json.dumps(dict, ensure_ascii=False))
    #輸出: {"key": "測試"}

6、AttributeError: ‘NoneType’ object has no attribute ‘decode’

(1)原因:

1、redis資料庫為空,未取到資料,返回型別是NoneType型別
2、錯誤例項:
    from redis import StrictRedis
    r = StrictRedis(host='localhost', port=6379)
    print(r.lpop('test').decode('utf-8'))

(2)解決方法:

1、確保redis裡面有資料,先存資料,再取資料
2、程式碼:
    import json
    from redis import StrictRedis
    r = StrictRedis(host='localhost', port=6379)
    dict = {'key': '測試'}
    data = json.dumps(dict, ensure_ascii=False)
    r.rpush('test', data)
    print(r.lpop('test').decode('utf-8')) #redis取出來的資料為位元組型別,需要編碼decode
    #輸出:{"key": "測試"}

7、如果代理設定成功,最後顯示的IP應該是代理的IP地址,但是最終還是我真實的IP地址,為什麼?

獲取代理並檢測有效性:https://github.com/Shirmay1/Python/blob/master/Proxyip/xici.py
(1)原因:

requests.get(url,headers=headers,proxies=proxies)
1、proxies在你訪問http協議的網站時用http代理,訪問https協議的網站用https的代理
2、所以你的proxy需要根據網站是http還是https的協議進行代理設定,這樣才能生效

(2)解決方法:

1、方法:
    如果proxies ={'http': 'http://112.85.169.206:9999'},
    http的測試網站'http://icanhazip.com'
    如果proxies ={'https': 'https://112.85.129.89:9999'}
    https的測試網站https://www.baidu.com/
2、程式碼:
    proxies ={'http': 'http://112.85.169.206:9999'}
    r = requests.get('http://icanhazip.com', headers=headers, proxies=proxies, timeout=2)
    proxies ={'https': 'https://112.85.129.89:9999'}
    r = requests.get('https://www.baidu.com/', headers=headers, proxies=proxies, timeout=2)
···

8、HTTPConnectionPool

(1) Max retries exceeded with url

1、報錯:
    a.HTTPConnectionPool(host='XXX', port=XXX): Max retries exceeded with url: XXXX(Caused by ProxyError('Cannot connect to proxy.', ConnectionResetError(10054, '遠端主機強迫關閉了一個現有的連線。', None, 10054, None)))
    b.HTTPConnectionPool(host='XXX', port=XXX): Max retries exceeded with url: XXXX(Caused by ProxyError('Cannot connect to proxy.', NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000020B87AAC4E0>: Failed to establish a new connection: [WinError 10061] 由於目標計算機積極拒絕,無法連線。')))
    c.HTTPConnectionPool(host='XXX', port=XXX): Max retries exceeded with url: XXXX (Caused by ProxyError('Cannot connect to proxy.', RemoteDisconnected('Remote end closed connection without response')))
2、原因:
    a.http連線太多沒有關閉導致
    b.訪問次數頻繁,被禁止訪問
    c.每次資料傳輸前客戶端要和伺服器建立TCP連線,為節省傳輸消耗,預設為keep-alive,即連線一次,傳輸多次,然而在多次訪問後不能結束並回到連線池中,導致不能產生新的連線
3、解決方法:
    a.增加連線次數
    b.requests使用了urllib3庫,預設的http connection是keep-alive的,requests設定False關閉。
    c.headers中的Connection預設為keep-alive,將headers中的Connection一項置為close
4、小知識補充:
    a.會話物件requests.Session能夠跨請求地保持某些引數,比如cookies,即在同一個Session例項發出的所有請求都保持同一個cookies,而requests模組每次會自動處理cookies,這樣就很方便地處理登入時的cookies問題。

(2)程式碼

"""增加重連次數,關閉多餘連線,使用代理"""
import requests
headers = {'Connection': 'close'}
requests.adapters.DEFAULT_RETRIES = 5 # 增加重連次數
s = requests.session()
s.keep_alive = False # 關閉多餘連線
s.proxies = {"https": "47.100.104.247:8080", "http": "36.248.10.47:8080", } # 使用代理
try:
    s.get(url) # 訪問網址
except Exception as err:
    pass
"""This will GET the URL and retry 3 times in case of requests.exceptions.ConnectionError. backoff_factor will help to apply delays between attempts to avoid to fail again in case of periodic request quo"""
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
try:
    s.get(url) # 訪問網址
except Exception as err:
    pass