爬蟲中常見問題
阿新 • • 發佈:2020-09-13
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