Selenium 模拟浏览器另存为

缘由

开发一个下载图片的爬虫,原网站时不时变动,导致爬虫经常会挂掉,尤其在最终下载图片的环节。想着模拟浏览器另存为的方式可能会更健壮些,不那么容易挂。另外也一直想学习下 Selenium。

Selenium

1. 驱动和环境

使用的是 Python 环境,Selenium 的安装直接 pip install selenium 即可。

浏览器使用的是 Firefox,驱动是 geckdriver。将下载后的文件放入到 Firefox 安装目录下,如 C:\Program Files\Mozilla Firefox

环境变量的配置也是 Firefox 安装目录,而不需要到文件。这里坑了好久,一直报找不到驱动的错误,原因是写成了 C:\Program Files\Mozilla Firefox\geckdriver.exe

2. 请求网站

以掘金网站博客 cxuan读者的外包面试之旅 为例。

driver = webdriver.Firefox()
driver.get("https://juejin.im/post/6857394127064006663")

此时没有问题的话,浏览器会自动打开网站。

3. 滚动加载

此时若直接获取图片,只会获取获取到一两张。因为大多数网站采用 Ajax 方式,页面滚动到图片才加载,所有就需要模拟页面滚动了。execute_script 方法调用 js 可实现滚动操作。

# 获取内容高度
all_height = driver.execute_script("return document.body.scrollHeight")
# 循环往下滚动,每次滚1024高,间隔1秒
scroll_height = 1024
for i in range(int(all_height/scroll_height)):
    driver.execute_script("window.scrollTo(0, {0});".format(i*scroll_height))
    time.sleep(1)

如果遇到无限加载的页面,可以参考 stackoverflow 的 回答。代码如下:

SCROLL_PAUSE_TIME = 0.5

# Get scroll height
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # Scroll down to bottom
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # Wait to load page
    time.sleep(SCROLL_PAUSE_TIME)

    # Calculate new scroll height and compare with last scroll height
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

4. 查找元素

图片加载完成后,就可以定位元素找到图片地址了。这里知识和之前学 Scrapy 的差不多。

# 定位到文章节点
image_content = driver.find_element_by_css_selector(".article")
# 获取文章内img标签
image_elems = image_content.find_elements_by_css_selector("img")
# 图片链接存放到list中
image_list = []
for i in image_elems:
    # print(i.get_attribute("src"))
    image_list.append(i.get_attribute("src"))

5. 调用右键

ActionChains 方法可调用右键。右键之前需先定位到元素。

driver = webdriver.Firefox()
driver.get("https://img-blog.csdnimg.cn/20191104151241389.png")
actionChains = ActionChains(driver)

body = driver.find_element_by_css_selector("body > img")
actionChains.context_click(body).perform()

time.sleep(3)
driver.close()

然后就没有然后了。

Selenium 好像对页面而无法右键菜单进行操作,网上也搜了好久,应该得联合 js 操作,感觉挺麻烦的。后来找到了 PyAutoGUI 这个库。

PyAutoGUI

安装直接 pip install pyautogui 即可。

使用超级简单:

  • write() 写入字符
  • press() 按下按键,相当于 keyDown()keyUp() 的包装
  • hotkey() 可以传递几个键字符串,这些字符串将按顺序按下,然后以相反的顺序释放

如火狐的另存为在第三个位置,只需要按方向键下三次,然后回车即可跳出保存对话框。代码如下:

pyautogui.press(['down','down', 'down', 'enter'])

由于是图片地址,本 DEMO 其实只需要键入 Ctrl + s 就可以跳出保存对话框。代码如下:

# 浏览器另存为保存图片
for i in image_list:
    driver.get(i)

    # 图片地址,直接 ctrl+s,不用另存为
    pyautogui.hotkey('ctrl', 's')
    pyautogui.sleep(1)
    # 采用时间戳命名,防止名称重复弹出确认框
    pyautogui.write(str(int(time.time() * 1000)), interval=0.1)
    pyautogui.press('enter')

    pyautogui.sleep(1)

整体代码如下:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains
import pyautogui
import time


def driver_JJ():
    # 请求网站
    driver = webdriver.Firefox()
    driver.get("https://juejin.im/post/6857394127064006663")

    # 获取内容高度
    all_height = driver.execute_script("return document.body.scrollHeight")
    # 循环往下滚动,每次滚1024高,间隔1秒
    scroll_height = 1024
    for i in range(int(all_height/scroll_height)):
        driver.execute_script("window.scrollTo(0, {0});".format(i*scroll_height))
        time.sleep(1)

    # 定位到文章节点
    image_content = driver.find_element_by_css_selector(".article")
    # 获取文章内img标签
    image_elems = image_content.find_elements_by_css_selector("img")
    # 图片链接存放到list中
    image_list = []
    for i in image_elems:
        print(i.get_attribute("src"))
        image_list.append(i.get_attribute("src"))

    # 浏览器另存为保存图片
    for i in image_list:
        driver.get(i)

        # 图片地址,直接 ctrl+s,不用另存为
        pyautogui.hotkey('ctrl', 's')
        pyautogui.sleep(1)
        # 采用时间戳命名,防止名称重复弹出确认框
        pyautogui.write(str(int(time.time() * 1000)), interval=0.1)
        pyautogui.press('enter')

        pyautogui.sleep(1)

    driver.close()


def main():
    driver_JJ()


if __name__ == '__main__':
    main()

关联阅读


Last modified on 2020-08-05