[python3.6]爬蟲實戰之爬取淘女郎圖片
原博主地址:http://cuiqingcai.com/1001.html
原博是python2.7寫的,並且隨著淘寶程式碼的改版,原博爬蟲已經不可用。
參考 http://minstrel.top/TaoBaoMM 這位博主跟我一樣最近正在學習爬蟲。
1 定個小目標
lcw先生聽說我即將爬取美女的照片,兩眼都亮了。沒錯,我要給他福利了(其實女生也很喜歡美女)。
所以,定個最小的目標:
1.在F盤建立美女資料夾
2.資料夾下按照淘女郎美人庫預設美人排序,抓取31個美女的資訊(因為一頁預設是30個人,不至於太少,也能太多要不然抓取時間太多,lcw的破電腦也裝不下)
3.每個以美人名字命名的資料夾下,取10張照片(內容小,別介)
2 抓取過程
進入淘女郎首頁之後,點選找模特,進入我們需要爬取的頁面。可以看到頁面上是預設tag在美人庫上。也即是有30位預設的美人出現在頁面。每一位有相應的照片以及個人資訊。30位美人下方,是頁碼和總美人數的資訊。因為我也是web出身。像這種資訊和數量都有變化的資訊展示,肯定不是靜態頁面。一般都是通過js動態載入而來。通過開發者工具(google瀏覽器,F12,
Json handle外掛 感謝小夥伴告訴我這個外掛),動態監控network.在查詢載入資訊的http時,我犯了個錯誤,一直以為返回的資訊應該是json資訊。這是我們post返回結果最常見的格式,但事實是返回的xhr資訊。這就是抓包工具用得少的下場,哭。
明確了type是xhr後,很快找到了這個:
https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8
在headers裡面 fromdata中檢視source可以看到引數列表:
q=&viewFlag=A&sortType=default&searchStyle=&searchRegion=city%3A&searchFansNum=¤tPage=2&pageSize=100
我們只需要currentPage這個引數。
https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8¤tPage=2可以立刻看到下一頁的資訊。
輸入這個網址,藉助json handle外掛得到:
可以看到頁面上清楚的顯示了當前頁面上30位佳麗的資訊,以及當前頁面,總共美女數目。
其實這些資訊也可以通過正則匹配來得到,不過相對於直接使用js獲取,肯定後者更快捷。
2 目標
1.找到模特總頁數
2.找到所得頁的模特的json格式的資訊,這一步主要為了獲取每個模特的id。只有知道id才能進入到她的主頁。這裡
有一個十分重要的資訊,是之前我忽略了的。就是動態頁面。爬蟲通過url可以得到一個頁面,也無法得到載入該頁面
時通過引數動態載入的內容。所以,這裡想要通過url直接得到模特的資訊內容是不可能的。這是靜態頁面與動態
頁面的區別。
3.找到相簿總頁數
4.找到所得相簿頁的json格式的資訊。與第二步相似。得到每個相簿的id.
5.通過相簿id得到每個相簿的url。通過該url爬取該相簿下的內容。
(身為一個大寫的直男,lcw同學在看到我的目標之後,提出了更進一步的要求:要篩選出理想身高理想顏值的mm。好,
後期為你實現。)
3 按照目標步驟寫程式
3.1 根據https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8¤tPage=1可以得到上述
圖片中展示的資訊。程式如下:
#!usr/bin/env/python
#encoding:UTF-8
import urllib
import requests
import json
import os
import re
import time
currentPage = 1
headers = {
'User-Agent': r'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT) '
r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3',
'Connection': 'keep-alive'
}
url = 'https://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'
try:
data = urllib.parse.urlencode({"currentPage":currentPage}).encode('utf-8')
request = urllib.request.Request(url,data = data, headers = headers)
res = urllib.request.urlopen(request).read().decode('gbk')
print(res)
except urllib.error.URLError as e:
if hasattr(e, "reason"):
print("連線失敗,錯誤原因:",e.reason)
注意,這裡的res是使用gbk。這個也是通過network中檢視response得到的。
3.2 點選一位模特(以朱琳為例)的主頁面,得到地址https://mm.taobao.com/self/model_album.htm?
spm=719.7800510.a312r.17.xcy6bS&user_id=176817195。spm不知道是什麼暫時不管。user_id已經通過上面的json
得到了。那麼怎麼得到動態載入得到的相簿資訊呢。F12,重新整理檢視,得到返回值為xhr的一個請求地址:https://mm.taobao.com/self/album/open_album_list.htm?_charset=utf-8&user_id%20=176817195。
在位址列輸入上面的地址得到圖片:
以上資訊雖然不是json格式的資訊,但是我們欣喜的發現,所需要的資訊全部在這裡。用爬蟲獲取上述url的html內容
相簿ID和相簿總頁數都得到了。其實跟json差不多。
多說一句。本來我檢視原始頁面,不是這個中間請求頁面時,想要根據下面這個獲得總的相簿頁數:
檢視:
的原始碼:
之前是希望通過上述程式碼得到總頁面數'9'. 發現通過https://mm.taobao.com/self/model_album.htm?user_id=17681
7195無法獲取上述Html.
只能找到載入該頁面時的動態請求https://mm.taobao.com/self/album/open_album_list.htm?_charset=utf-8&user
_id%20=176817195的原始碼,從而得到:
老實說這種方法真的很笨。不知道有沒有更簡易的方式。
找到相簿ID後。,通過https://mm.taobao.com/self/album_photo.htm?user_id=176817195&album_id=10000962815
點選進入相簿,同樣查詢xhr檔案,找到:https://mm.taobao.com/self/album/album_photo_list.htm?user_id=
176817195&album_id=301783179&album_flag=0,直接開啟該連線原始碼找到:
https://mm.taobao.com/album/json/get_album_photo_list.htm?user_id=176817195&album_id=10000794223&page=1
這總算找到了相簿的資訊。
這個資訊簡直要什麼有什麼啊。
好了。
現在開始寫程式:
#!usr/bin/env/python
#encoding:UTF-8
import urllib
import requests
import json
import os
import re
import time
class MMSpider:
def __init__(self):
self.__code_type = 'gbk'
self.__http = 'http:'
# 美人庫動態載入xhr資料的url
self.__url = 'http://mm.taobao.com/tstar/search/tstar_model.do?_input_charset=utf-8'
# 模特主頁地址
self.__person_url = 'http://mm.taobao.com/self/aiShow.htm?userId='
#相簿地址
self.__all_album_url = 'https://mm.taobao.com/self/album/open_album_list.htm?_charset=utf-8&user_id%20='
# 具體相簿地址
self.__pic_url = "https://mm.taobao.com/album/json/get_album_photo_list.htm?user_id="
# 儲存照片的基地址
self.__save_path = 'F:\Beauty'
# 想要獲取的頁數
self.__total_page = 1
# 當前正在提取的頁數
self.__currentPage = 1
# 找到具體album的id的正則表示式
self.__album_id_pattern = re.compile('''<h4>.*?album_id=(.*?)&''', re.S)
# album有多少的的正則表示式
# 找到具體album的id的正則表示式
self.__album_page_pattern = re.compile('''<input name="totalPage" id="J_Totalpage" value="(.*?)"''', re.S)
#根據動態請求獲取需要的第X頁的json資料,找出userId
def get_person_dict(self, currentPage):
try:
data = {
"currentPage":currentPage
}
data = urllib.parse.urlencode(data).encode('utf-8')
request = urllib.request.Request(self.__url, data=data)
response = urllib.request.urlopen(request)
result = response.read().decode(self.__code_type)
return json.loads(result)
except urllib.error.URLError as e:
print('美人動態載入資訊出錯',e.reason)
# 根據得到的userID,找到相簿的總頁數
def get_album_page(self, userId):
try:
all_album_url = self.__all_album_url+str(userId)
res = urllib.request.urlopen(all_album_url)
html = res.read().decode(self.__code_type)
return re.search(self.__album_page_pattern, html).group(1)
except urllib.error.URLError as e:
print('動態載入相簿總資訊出錯',e.reason)
return None
# 由得到的相簿總頁數範圍內指定的頁數,獲取該頁所有相簿的ID
def get_album_ids(self, userId, page):
try:
all_album_url = self.__all_album_url + str(userId) + "&" + str(page)
request = urllib.request.Request(all_album_url)
response = urllib.request.urlopen(request)
html = response.read().decode(self.__code_type)
# 提取該頁中album的id
return re.findall(self.__album_id_pattern, html)
except urllib.error.URLError as e:
print("提取相簿id出錯了!", e.reason)
# 找到一個相簿內的圖片有多少頁
def get_pic_page(self, userId, albumId):
try:
# 先得到這個相簿一共有多少頁
url = self.__pic_url + str(userId) + "&album_id=" + str(albumId)
response = urllib.request.urlopen(url)
result = json.loads(response.read().decode(self.__code_type))
return result["totalPage"]
except urllib.error.URLError as e:
print(e.reason)
return None
# 根據相簿圖片頁數獲取指定頁數的圖片資訊
def get_img_url(self, person, j, album_id):
url = self.__pic_url + str(person["userId"]) \
+ "&album_id=" + str(album_id) \
+ "&page=" + str(j)
try:
response = urllib.request.urlopen(url, timeout=5)
result = response.read().decode(self.__code_type)
imgs_url = json.loads(result)["picList"]
return imgs_url
except TimeoutError as e:
print('1',e.strerror)
except urllib.error.URLError as e:
print('2',e.reason)
except BaseException as e:
print('3',e.args)
# 儲存model的個人資訊
def save(self, searchDOList):
for person in searchDOList:
dir_path = self.__save_path+'\\'+person['realName']
if self.mkdir(dir_path):
txt_path = dir_path+'\\'+person['realName']+'.txt'
self.write_txt(txt_path, person)
self.save_imgs(person,dir_path)
def mkdir(self, dir_path):
if(os.path.exists(dir_path)):
return False
else:
os.mkdir(dir_path)
return True
def write_txt(self,txt_path, person):
person_url = self.__person_url+str(person['userId'])
content = "姓名:" + person["realName"] + " 城市:" + person["city"] \
+ "\n身高:" + str(person["height"]) + " 體重:" + str(person["weight"]) \
+ "\n喜歡:" + str(person["totalFavorNum"]) \
+ "\n個人主頁:" + person_url
with open(txt_path, 'w',encoding='utf-8')as file:
print('正在儲存%s的文字資訊'%(person['realName']))
file.write(content)
file.close()
def save_imgs(self, person, dir_path):
album_page = self.get_album_page(person['userId'])
print(album_page)
img_index = 1
for i in range(1, int(album_page)+1):
album_ids =self.get_album_ids(person["userId"],i)
for album_id in album_ids:
pic_page = self.get_pic_page(person["userId"],album_id)
for j in range(1, int(pic_page)+1):
img_urls = self.get_img_url(person,j,album_id)
for img_url in img_urls:
try:
url = self.__http+img_url["picUrl"]
res = urllib.request.urlopen(url, timeout =5)
with open(dir_path+'\\'+str(img_index)+'.jpg','wb') as file:
file.write(res.read())
if img_index % 10 ==0:
print('sleep 1 second')
time.sleep(1)
if img_index>=11:
print('%s已儲存11張辣照'%person['realName'])
file.close()
return
img_index +=1
except TimeoutError as e:
print('1',e.strerror)
except urllib.error.URLError as e:
print('2',e.reason)
def start(self):
print("開始!")
opener = urllib.request.build_opener()
opener.addheaders = [("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36")]
urllib.request.install_opener(opener)
for i in range(self.__total_page):
dict_result = self.get_person_dict(self.__currentPage)
searchDOList = dict_result["data"]["searchDOList"]
# 儲存所有本頁中MM的資訊
self.save(searchDOList)
self.__currentPage += 1
if __name__=="__main__":
spider = MMSpider()
spider.start()