[python3.6]爬虫实战之爬取淘女郎图片

时间:2022-07-07 05:04:54

原博主地址: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&currentPage=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&currentPage=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。

在地址栏输入上面的地址得到图片:

[python3.6]爬虫实战之爬取淘女郎图片

以上信息虽然不是json格式的信息,但是我们欣喜的发现,所需要的信息全部在这里。用爬虫获取上述url的html内容


相册ID和相册总页数都得到了。其实跟json差不多。

多说一句。本来我查看原始页面,不是这个中间请求页面时,想要根据下面这个获得总的相册页数:

查看:

[python3.6]爬虫实战之爬取淘女郎图片

的源码:

[python3.6]爬虫实战之爬取淘女郎图片

之前是希望通过上述代码得到总页面数'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的源码,从而得到:

[python3.6]爬虫实战之爬取淘女郎图片

老实说这种方法真的很笨。不知道有没有更简易的方式。

找到相册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

这总算找到了相册的信息。

[python3.6]爬虫实战之爬取淘女郎图片

这个信息简直要什么有什么啊。

好了。

现在开始写程序:

#!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()