Python 自动爬取花瓣网任意面板中所有图片

7 minute

注:该项目只适用于旧版花瓣,但其中的思路值得领会。对于新版花瓣编写的代码,逻辑差别不大。

需要安装的库

  • urllib
  • easygui
  • selenium
  • webdriver_manager

获取过程

  1. 进入面板内
  2. 复制当前面板 url
  3. 启动该脚本按提示进行即可

代码编写流程

分析 pin 图特点

查看面板源码,可以在对应的 script 中找到面板中图片的 json 数据。

在 app.page[“board”] 下可以找到 “pins”:[{…}],主要图片 ID(pin) 位于这里面。

获取到图片的 ID(pin) 之后可以对应访问点击图片后进入的地址 http://huaban.com/pins/pinId/,并获取页面源码:

显然可见主要图片的源码特征,书写对应正则表达式可以获取图片真实地址。

分析滚动特点

通过滚动页面我们可以发现加载规律

原来的图片对应的代码:

经过滚动,原来的代码逐渐被一些新的代码取代:

而不难发现他们都有对应的data-id!而 data-id 就是图片地址中对应的 pin

所以我们可以通过 webdriver 滚动加载页面每滚动一次就进行一次 data-id 的读取,并利用集合进行去重即可。

具体实现

  1# 花瓣用户任意面板图片爬取
  2import urllib.request
  3import urllib.error
  4import re
  5import os
  6import datetime
  7import easygui
  8import time
  9from selenium import webdriver
 10from selenium.webdriver.chrome.service import Service
 11from selenium.webdriver.common import service
 12from webdriver_manager import driver
 13from webdriver_manager.chrome import ChromeDriverManager
 14import winsound
 15
 16# 获取网页中所有图片对应的pin
 17def get_pins(url_, num_):
 18    my_options = webdriver.ChromeOptions()
 19    my_options.add_argument("start-maximized")
 20    my_options.add_argument("--ignore-certificate-errors")
 21    my_options.add_argument("--ignore-ssl-errors")
 22    my_options.add_experimental_option("excludeSwitches", ['enable-automation', 'enable-logging'])
 23    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=my_options)
 24    # 如果当前chromedriver版本不对,会自动去官网下载对应的版本到~/.wdm/drivers/chromedriver里,且会有缓存,下次直接读取缓存
 25    driver.get(url_)
 26
 27    js = "window.scrollTo(0,document.body.scrollHeight);" # js脚本实现向下滑动
 28    pattern = re.compile(r'data-id=?"(\d*)"')
 29    pins_ = set()
 30    # 可以使用大括号 { } 或者 set() 函数创建集合,但是注意如果创建一个空集合必须用 set() 而不是 { },因为{}是用来表示空字典类型的。
 31    num = int(num_)
 32    tries = num / 15 + 1; # 滑动次数,每次滑动大约可以获得15张图片的pin
 33    while True:
 34        tries -= 1
 35        html_ = driver.page_source
 36        pins_1 = re.findall(pattern, html_) # 返回获取到的pin列表
 37        pins_2 = set(pins_1) # pin列表转化为pin集合
 38        pins_.update(pins_2) # 求交集,以免下载重复照片
 39        driver.execute_script(js)
 40        time.sleep(1) # 给脚本运行留足时间
 41        if tries < 0:
 42            break
 43    driver.close()
 44    return pins_
 45
 46
 47# 获取页面html
 48def get_html_1(url_):
 49    try:
 50        page = urllib.request.urlopen(url_)
 51    except urllib.error.URLError:
 52        return 'fail'
 53    html_ = page.read().decode('utf-8')
 54    return html_
 55
 56
 57# 下载图片
 58def get_image(path_, pin_list):
 59    success = 0
 60    fail = 0
 61    now = time.time()
 62    t_now = time.strftime("%Y-%m-%d_%H-%M-%S_", time.localtime(now))
 63    for pinId in pin_list:
 64        # 获取跳转网页网址
 65        url_str = r'http://huaban.com/pins/%s/' % pinId
 66
 67        # 获取点击图片时弹出网页的源码
 68        pinId_source = get_html_1(url_str)
 69        if pinId_source == 'fail':
 70            continue
 71
 72        # 解析源码,获取原图片的网址
 73        '''
 74        <div class="main-image"><div class="image-holder" id="baidu_image_holder">
 75        <img src="//hbimg.huabanimg.com/64369267b9c8dc7a43da81457658c05b1a752f9329ec0-dSfdfl_fw658/format/webp"
 76        '''
 77        img_url_re = re.compile('main-image.*?src="(.*?)"', re.S)
 78        img_url_list = re.findall(img_url_re, pinId_source)
 79        img_url = 'http:' + img_url_list[0]
 80        try:
 81            urllib.request.urlretrieve(img_url, path_ + "\\" + t_now + str(success) + ".jpg")
 82            # urlretrieve()方法直接将远程数据下载到本地
 83        except urllib.error.URLError:
 84            print("获取失败!%s" % img_url)
 85            fail += 1
 86            continue
 87        print("获取成功!%s" % img_url)
 88        success += 1
 89    print("成功获取图片张数:%d" %success)
 90    print("获取失败的图片张数:%d" %fail)
 91    winsound.Beep(frequency=250, duration=500)
 92    os.system("pause")
 93
 94
 95# 创建文件夹路径
 96def createPath():
 97    while True:
 98        path_ = easygui.diropenbox(title='选择你要保存的路径')
 99        filePath = path_ + "\\" + str(datetime.datetime.now().strftime('%Y-%m-%d %H.%M.%S'))
100
101        isExists = os.path.exists(filePath)
102        if not isExists:
103            # 创建目录
104            os.makedirs(filePath)
105            print('%s创建成功!' % filePath)
106            break
107        else:
108            print('%s已存在重新输入!' % filePath)
109    return filePath
110
111
112if __name__ == '__main__':
113    # 用户输入数据
114    values = easygui.multenterbox("获取花瓣用户任意面板中的图片", "welcome", ["面板的地址", "想下载的大概张数"])
115    url = values[0]
116    num = values[1]
117    path = createPath()
118
119    # 获取图片的pin
120    pins = get_pins(url, num)
121
122    # 获取并下载图片
123    print('即将为主人下载%d张照片~' %len(pins))
124    get_image(path, pins)