擷取待爬取頁面
今天繼續爬取一個網站,
http://www.27270.com/ent/meinvtupian/
這個網站具備反爬,so我們下載下傳的代碼有些地方處理的也不是很到位,大家重點學習思路,有啥建議可以在評論的地方跟我說說。

為了以後的網絡請求操作方向,我們這次簡單的進行一些代碼的封裝操作。
在這裡你可以先去安裝一個叫做
retrying
的子產品
pip install retrying
這個子產品的具體使用,自己去百度吧。嘿嘿哒~
在這裡我使用了一個随機産生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次之後,在報錯!
同時在
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
資料流
下面基于這個私有方法,增加一個擷取網絡文本的方法和一個擷取網絡檔案的方法。同步完善類的初始化方法,在開發中發現,我們要爬取的網頁編碼是
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)]
批量把要爬取的所有連結都生成完畢。
分析上面爬取到的URL清單,捕獲詳情頁
我們采用生産者和消費者模型,就是一個抓取連結圖檔,一個下載下傳圖檔,采用多線程的方式進行操作,需要首先引入
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()
一會過後,就慢慢收圖吧
這個版本的代碼有點問題,對反爬處理的并不是很到位,大家先學習基礎的,這個地方找時間,我在完善一下。
隐藏彩蛋,重要的事情說100遍:爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門,爬蟲入門