[Python3] 爬取百度图片到本地

前言

因为需要一些图片素材,又不想一个个手动下载,遂通过爬虫来解放双手。在百度图片中搜索“汉服美女”,然后以浏览器地址栏上的地址作为初始 URL。通过对 URL 分析知道 URL 分为 3 部分:域名 + 固定参数 + 关键字参数。

爬取

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# @author: Nancy
# @contact: [email protected]
# @software: PyCharm
# @file: getHanfu.py
# @time: 2019/2/23 14:34

import requests
import re
import time


class BaiduPictures(object):
    def __init__(self, keyboard):
        self.headers = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 Safari/537.36"}
        self.base_url = "https://image.baidu.com"
        self.keyboard = str(keyboard)

    def send_request(self, url):
        """
        :param url: 网址
        :return: unicode 型数据
        """
        try:
            html = requests.get(url, headers=self.headers).text
            return html
        except Exception as e:
            print(e)

    def make_request(self, url):
        """
        :param url: 网址
        :return: bytes 型数据 (二进制的数据)
        """
        try:
            html = requests.get(url, headers=self.headers).content
            return html
        except Exception as e:
            print(e)

    def load_page(self, html):
        pattern = r'"objURL":"(http.*?)",'
        img_urls = re.findall(pattern, html)
        for img_url in img_urls:
            img_puffix = img_url.rsplit(".", 1)[1].lower()
            t = time.time()
            now_time = str(round(t * 1000))
            data = self.make_request(img_url)
            if data and img_puffix in ['jpg', 'jpeg', 'png']:
                self.write_pic(data, now_time + "." + img_puffix)
            elif data:
                self.write_pic(data, now_time + ".jpg")
            else:
                print(img_url + u"地址无效")
                # data = self.make_request(img_url + ".jpg")
                # if data:
                #     self.write_pic(data, now_time + ".jpg")

    def write_pic(self, data, filename):
        print(u"[INFO]: 正在下载:%s..." % filename)
        with open(u"E:\images\img" + filename, 'wb') as f:
            f.write(data)

    def start(self):
        html = self.send_request(self.base_url + "/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1" +
                                                 "&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0" +
                                                 "&istype=2&ie=utf-8&fm=index&pos=history&word=" + self.keyboard)
        self.load_page(html)


if __name__ == "__main__":
    keyboard = input("请输入关键词:")
    Pictures = BaiduPictures(keyboard="汉服美女")
    Pictures.start()

注意:上述代码中会出现图片地址中无后缀,如下图所示。
[Python3] 爬取百度图片到本地_第1张图片

进阶

通过上方代码,我们会发现只能下载 30 张图片,这是因为百度图片是动态加载的,它的网页原始数据其实是没有这个图片的,通过运行 JavaScript,把这个图片数据插入到网页的 html 标签中,那这样造成的结果是,我们在开发者工具中虽然能看到这个 html 标签,但实际上,当我们在看网页的原始数据的时候,其实是没有这个标签的,它只在运行时加载和渲染,那这个时候怎么办呢?怎么把这个图片给下载下来呢?这里面我们就换一个思路:抓包。

按F12打开开发者工具,点击 Network–XHR,然后向下滑动滚动条,会一直出现一个名为:acjson?tn=resultjson_com…的请求,点击它再点击 Preview,会发现这是 json 数据,点开 data,会看到里面有 30 条数据,每一条都对应着一张图片。

这下明白了吧,百度图片一开始只加载 30 张图片,当我们往下滑动滚动条时,页面会动态加载 1 条 json 数据,每条 json 数据包含 30 条信息,信息里又包含图片的 URL,JavaScript 会将这些 URL 解析并显示出来。这样,每次滚动到底就又多出 30 张图片。

那么,这些一直出现的 json 数据有什么规律呢?我们点击 Headers,然后对比这些 json 数据的头部信息。通过对比,我们发现 Headers 下的 Query String Parameters 中的字段大多保持不变,只有 pn、gsm 字段不同,而 pn 每次递增 30,以及最后的时间戳不断变化。
[Python3] 爬取百度图片到本地_第2张图片

代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# @author: Nancy
# @contact: [email protected]
# @software: PyCharm
# @file: getHanfu.py
# @time: 2019/2/23 14:34

import requests
import re
import time


class BaiduPictures(object):
    def __init__(self, keyboard):
        self.headers = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 Safari/537.36"}
        self.base_url = "https://image.baidu.com"
        self.keyboard = str(keyboard)

    def send_request(self, url):
        """
        :param url: 网址
        :return: unicode 型数据
        """
        try:
            html = requests.get(url, headers=self.headers).text
            return html
        except Exception as e:
            print(e)

    def make_request(self, url):
        """
        :param url: 网址
        :return: bytes 型数据 (二进制的数据)
        """
        try:
            html = requests.get(url, headers=self.headers).content
            return html
        except Exception as e:
            print(e)

    def load_page(self, html):
        pattern = r'"objURL":"(ippr.*?)",'
        encrypted_urls = re.findall(pattern, html)
        for encrypted_url in encrypted_urls:
            t = time.time()
            now_time = str(round(t * 1000))                  # 时间戳
            img_url = self.url_complie(encrypted_url)        # 解密 objURL 地址
            img_puffix = img_url.rsplit(".", 1)[1].lower()   # 图片格式后缀
            data = self.make_request(img_url)
            if data and img_puffix in ['jpg', 'jpeg', 'png']:
                self.write_pic(data, now_time + "." + img_puffix)
            elif data:
                self.write_pic(data, now_time + ".jpg")
            else:
                print(img_url + u"地址无效")
                # data = self.make_request(img_url + ".jpg")
                # if data:
                #     self.write_pic(data, now_time + ".jpg")

    def url_complie(self, url):
        """
        :param url: 加密的 objURL 地址
        ex: ippr_z2C$qAzdH3FAzdH3Ft42_z&e3Bh7p55b_z&e3Bv54AzdH3F7rs5w1AzdH3Ft4w2jAzdH3Fm90nlb0mAzdH3Fdc_lmaxc9a_z&e3B3r2
        :return: 解密 URL
        """
        res = ''
        c = ['_z2C$q', '_z&e3B', 'AzdH3F']
        d = {'w': 'a', 'k': 'b', 'v': 'c', '1': 'd', 'j': 'e', 'u': 'f', '2': 'g', 'i': 'h', 't': 'i', '3': 'j',
             'h': 'k', 's': 'l', '4': 'm', 'g': 'n', '5': 'o', 'r': 'p', 'q': 'q', '6': 'r', 'f': 's', 'p': 't',
             '7': 'u', 'e': 'v', 'o': 'w', '8': '1', 'd': '2', 'n': '3', '9': '4', 'c': '5', 'm': '6', '0': '7',
             'b': '8', 'l': '9', 'a': '0', '_z2C$q': ':', '_z&e3B': '.', 'AzdH3F': '/'}

        if url is None or 'http' in url:
            return url
        else:
            j = url
            for m in c:
                j = j.replace(m, d[m])
            for char in j:
                if re.match('^[a-w\d]+$', char):
                    char = d[char]
                res = res + char
            return res

    def write_pic(self, data, filename):
        print(u"[INFO]: 正在下载:%s..." % filename)
        with open(u"E:\images\img" + filename, 'wb') as f:
            f.write(data)

    def start(self):
        for page in (30, 121, 30):
            html = self.send_request(self.base_url + "/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=" + self.keyboard +
                                                 "&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&hd=&latest=©right=&word=" + self.keyboard +
                                                 "&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&force=&pn=" + str(page) +
                                                 "&rn=30&gsm=1e&1551881855622=")
            print(self.load_page(html))


if __name__ == "__main__":
    keyboard = input("请输入关键词:")
    Pictures = BaiduPictures(keyboard="汉服美女")
    Pictures.start()

参考

python requests的content和text方法的区别

python百度图片爬取
python学习(7):python爬虫之爬取动态加载的图片,以百度图片为例
百度返回的JSON数据解析返回的objURL(python版本)

你可能感兴趣的:(Python3)