今天繼續爬取一個網站,http://www.27270.com/ent/meinvtupian/
這個網站具有反爬,so咱們下載的代碼有些地方處理的也不是很到位,你們重點學習思路,有啥建議能夠在評論的地方跟我說說。html
爲了之後的網絡請求操做方向,咱們此次簡單的進行一些代碼的封裝操做。python
在這裏你能夠先去安裝一個叫作 retrying
的模塊git
pip install retrying
這個模塊的具體使用,本身去百度吧。嘿嘿噠~github
在這裏我使用了一個隨機產生user_agent的方法正則表達式
import requests from retrying import retry import random import datetime class R: def __init__(self,method="get",params=None,headers=None,cookies=None): # do something def get_headers(self): user_agent_list = [ \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1" \ "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", \ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", \ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", \ "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \ "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", \ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", \ "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] UserAgent = random.choice(user_agent_list) headers = {'User-Agent': UserAgent} return headers #other code
retrying
最簡單的使用就是給你想不斷重試的方法加上 裝飾器 @retry 服務器
在這裏,我但願網絡請求模塊嘗試3次以後,在報錯!cookie
同時在R類
初始化方法中增長一些必備的參數,你能夠直接看下面的代碼網絡
__retrying_requests
方法爲私有方法,其中根據get
和post
方式進行邏輯判斷多線程
import requests from retrying import retry import random import datetime class R: def __init__(self,method="get",params=None,headers=None,cookies=None): #do something def get_headers(self): # do something @retry(stop_max_attempt_number=3) def __retrying_requests(self,url): if self.__method == "get": response = requests.get(url,headers=self.__headers,cookies=self.__cookies,timeout=3) else: response = requests.post(url,params=self.__params,headers=self.__headers,cookies=self.__cookies,timeout=3) return response.content # other code
網絡請求的方法已經聲明完畢,而且返回 response.content
數據流app
下面基於這個私有方法,增長一個獲取網絡文本的方法和一個獲取網絡文件的方法。同步完善類的初始化方法,在開發中發現,咱們要爬取的網頁編碼是gb2312
因此還須要給某些方法增長一個編碼參數
import requests from retrying import retry import random import datetime class R: # 類的初始化方法 def __init__(self,method="get",params=None,headers=None,cookies=None): self.__method = method myheaders = self.get_headers() if headers is not None: myheaders.update(headers) self.__headers = myheaders self.__cookies = cookies self.__params = params def get_headers(self): # do something @retry(stop_max_attempt_number=3) def __retrying_requests(self,url): # do something # get請求 def get_content(self,url,charset="utf-8"): try: html_str = self.__retrying_requests(url).decode(charset) except: html_str = None return html_str def get_file(self,file_url): try: file = self.__retrying_requests(file_url) except: file = None return file
到此,這個R類
已經被咱們完善了,完整的代碼,你應該從上面拼湊起來,你也能夠直接翻到文章最後面,去github上直接查閱。
接下來,就是比較重要的爬蟲代碼部分了。這一次,咱們能夠簡單的使用一下類和對象,而且加上簡單的多線程操做。
首先,建立一個 ImageList
類,這個類第一件事情,須要獲取咱們爬取頁面的總頁碼數目
這個步驟比較簡單
import http_help as hh # 這個http_help 是我上面寫到的那個R類 import re import threading import time import os import requests # 獲取全部待爬取的URL列表 class ImageList(): def __init__(self): self.__start = "http://www.27270.com/ent/meinvtupian/list_11_{}.html" # URL模板 # 頭文件 self.__headers = {"Referer":"http://www.27270.com/ent/meinvtupian/", "Host":"www.27270.com" } self.__res = hh.R(headers=self.__headers) # 初始化訪問請求 def run(self): page_count = int(self.get_page_count()) if page_count==0: return urls = [self.__start.format(i) for i in range(1,page_count)] return urls # 正則表達式匹配末頁,分析頁碼 def get_page_count(self): # 注意這個地方須要傳入編碼 content = self.__res.get_content(self.__start.format("1"),"gb2312") pattern = re.compile("<li><a href='list_11_(\d+?).html' target='_self'>末頁</a></li>") search_text = pattern.search(content) if search_text is not None: count = search_text.group(1) return count else: return 0 if __name__ == '__main__': img = ImageList() urls = img.run()
上面的代碼注意get_page_count
方法,該方法已經獲取到了末尾的頁碼
咱們在run
方法內部,經過一個列表生成器
urls = [self.__start.format(i) for i in range(1,page_count)]
批量把要爬取的全部連接都生成完畢。
咱們採用生產者和消費者模型,就是一個抓取連接圖片,一個下載圖片,採用多線程的方式進行操做,須要首先引入
import threading import time
完整代碼以下
import http_help as hh import re import threading import time import os import requests urls_lock = threading.Lock() #url操做鎖 imgs_lock = threading.Lock() #圖片操做鎖 imgs_start_urls = [] class Product(threading.Thread): # 類的初始化方法 def __init__(self,urls): threading.Thread.__init__(self) self.__urls = urls self.__headers = {"Referer":"http://www.27270.com/ent/meinvtupian/", "Host":"www.27270.com" } self.__res = hh.R(headers=self.__headers) # 連接抓取失敗以後從新加入urls列表中 def add_fail_url(self,url): print("{}該URL抓取失敗".format(url)) global urls_lock if urls_lock.acquire(): self.__urls.insert(0, url) urls_lock.release() # 解鎖 # 線程主要方法 def run(self): print("*"*100) while True: global urls_lock,imgs_start_urls if len(self.__urls)>0: if urls_lock.acquire(): # 鎖定 last_url = self.__urls.pop() # 獲取urls裏面最後一個url,而且刪除 urls_lock.release() # 解鎖 print("正在操做{}".format(last_url)) content = self.__res.get_content(last_url,"gb2312") # 頁面注意編碼是gb2312其餘格式報錯 if content is not None: html = self.get_page_list(content) if len(html) == 0: self.add_fail_url(last_url) else: if imgs_lock.acquire(): imgs_start_urls.extend(html) # 爬取到圖片以後,把他放在待下載的圖片列表裏面 imgs_lock.release() time.sleep(5) else: self.add_fail_url(last_url) else: print("全部連接已經運行完畢") break def get_page_list(self,content): # 正則表達式 pattern = re.compile('<li> <a href="(.*?)" title="(.*?)" class="MMPic" target="_blank">.*?</li>') list_page = re.findall(pattern, content) return list_page
上述代碼中比較重要的有
threading.Lock() 鎖的使用,在多個線程之間操做全局變量,須要進行及時的鎖定;
其餘的注意內容,我已經添加在註釋裏面,只要你按着步驟一點點的寫,而且加入一些本身微妙的理解,就能夠搞定。
到如今爲止,咱們已經抓取到了全部的圖片地址,我把他存放在了一個全局的變量裏面 imgs_start_urls
那麼如今又來了
這個列表裏面存放的是 http://www.27270.com/ent/meinvtupian/2018/298392.html
這樣的地址,當你打開這個頁面以後,你會發現只有一張圖片 ,而且下面有個分頁。
點擊分頁以後,就知道規律了
http://www.27270.com/ent/meinvtupian/2018/298392.html http://www.27270.com/ent/meinvtupian/2018/298392_2.html http://www.27270.com/ent/meinvtupian/2018/298392_3.html http://www.27270.com/ent/meinvtupian/2018/298392_4.html ....
當你進行屢次嘗試以後,你會發現,後面的連接徹底能夠靠拼接完成,若是沒有這個頁面,那麼他會顯示?
好了,若是你進行了上面的操做,你應該知道接下來怎麼實現啦!
我把全部的代碼,都直接貼在下面,仍是用註釋的方式給你們把最重要的地方標註出來
class Consumer(threading.Thread): # 初始化 def __init__(self): threading.Thread.__init__(self) self.__headers = {"Referer": "http://www.27270.com/ent/meinvtupian/", "Host": "www.27270.com"} self.__res = hh.R(headers=self.__headers) # 圖片下載方法 def download_img(self,filder,img_down_url,filename): file_path = "./downs/{}".format(filder) # 判斷目錄是否存在,存在建立 if not os.path.exists(file_path): os.mkdir(file_path) # 建立目錄 if os.path.exists("./downs/{}/{}".format(filder,filename)): return else: try: # 這個地方host設置是個坑,由於圖片爲了防止盜鏈,存放在另外一個服務器上面 img = requests.get(img_down_url,headers={"Host":"t2.hddhhn.com"},timeout=3) except Exception as e: print(e) print("{}寫入圖片".format(img_down_url)) try: # 圖片寫入不在贅述 with open("./downs/{}/{}".format(filder,filename),"wb+") as f: f.write(img.content) except Exception as e: print(e) return def run(self): while True: global imgs_start_urls,imgs_lock if len(imgs_start_urls)>0: if imgs_lock.acquire(): # 鎖定 img_url = imgs_start_urls[0] #獲取到連接以後 del imgs_start_urls[0] # 刪掉第0項 imgs_lock.release() # 解鎖 else: continue # http://www.27270.com/ent/meinvtupian/2018/295631_1.html #print("圖片開始下載") img_url = img_url[0] start_index = 1 base_url = img_url[0:img_url.rindex(".")] # 字符串能夠當成列表進行切片操做 while True: img_url ="{}_{}.html".format(base_url,start_index) # url拼接 content = self.__res.get_content(img_url,charset="gbk") # 這個地方獲取內容,採用了gbk編碼 if content is not None: pattern = re.compile('<div class="articleV4Body" id="picBody">[\s\S.]*?img alt="(.*?)".*? src="(.*?)" />') # 匹配圖片,匹配不到就表明本次操做已經完畢 img_down_url = pattern.search(content) # 獲取到了圖片地址 if img_down_url is not None: filder = img_down_url.group(1) img_down_url = img_down_url.group(2) filename = img_down_url[img_down_url.rindex("/")+1:] self.download_img(filder,img_down_url,filename) #下載圖片 else: print("-"*100) print(content) break # 終止循環體 else: print("{}連接加載失敗".format(img_url)) if imgs_lock.acquire(): # 鎖定 imgs_start_urls.append(img_url) imgs_lock.release() # 解鎖 start_index+=1 # 上文描述中,這個地方須要不斷進行+1操做
全部的代碼都在上面了,關鍵的地方我儘可能加上了標註,你能夠細細的看一下,實在看不明白,就多敲幾遍,由於沒有特別複雜的地方,好多都是邏輯。
最後附上main部分的代碼,讓咱們的代碼跑起來
if __name__ == '__main__': img = ImageList() urls = img.run() for i in range(1,2): p = Product(urls) p.start() for i in range(1,2): c = Consumer() c.start()
一會事後,就慢慢收圖吧