草庐IT

【爬虫】Selenium爬取动态网页的base64图片

_Morain 2023-11-06 原文

1,需求

近日遇到需要爬取某网站的一些图片。图片所在页面为基础地址加上图片集的编号(类似:www.XXX.com/img/001,其为restful风格的网址,后面的数字为图片集的编号)。进入页面后,由动态加载网页,其技术以我现在的水平还无从得知。图片以base64风格嵌在img标签中。我的目标就是要爬取所有图集中的图片,将其保存为png或jpg格式。

2,环境和使用的技术

  • 使用的python版本是Python 3.7.0

  • 因为是动态加载的页面,在Chrome浏览器的network中抓包找不到相关加载base64的请求,目前我只知道可以使用Selenium

  • 获取加载完成后的网页源码后,使用bs4(BeautifulSoup)解析获取想要的base64字符串,当然也可以使用xpath等。

安装环境

如果未安装需要的库,可以直接进入CMD使用pip安装(PyCharm的话还要调整路径,VSCode可以直接安装,PyCharm可参考其他文章)

# 安装selenium
pip install selenium
# 安装bs4
pip install bs4

# 如果下载慢可以用国内镜像,只需要在上述命令后添加: -i 国内python镜像地址
# 如:
# 安装selenium
pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装bs4
pip install bs4 -i https://pypi.tuna.tsinghua.edu.cn/simple

3,难点

3.1,页面完整加载缓慢
  • 问题

    爬取的网站页面上个别部分加载比较慢,网页加载完成所需要的时间很长。页面加载过程线程阻塞,要页面停止加载才会执行后面的代码,每爬一个页面加载时间都很长,导致爬取速度很慢。

  • 解决方案

    虽然加载完成很慢,但是所需要的图片几秒钟就可以加载完成,所以我想着可不可以直接代码控制网页自动停止加载。

    • 首先我想到的是直接调用js的停止执行命令,但是发现线程阻塞,不能够使用

      '''这里只演示关键部分,表达思想,省略了一些代码和参数'''
      url = 'www.XXX.com/img/001'
      broswer = webdriver.Chrome()
      broswer.get(url)	# 只有页面加载完成后,才能执行下面代码,也就是页面加载完成后,才能执行停止页面加载的代码...
      browser.execute_script('window.stop()')
      
    • 一通操作后,我在网上找到了让get不阻塞线程的方法,使得自己能够控制加载时间:

      from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
      
      LOAD_TIME = 3
      path = "C:\...\chrome.exe"
      options = Options()
      ##### 设置get不阻塞线程 #####
      desired_capabilities = DesiredCapabilities.CHROME
      desired_capabilities["pageLoadStrategy"] = "none"
      broswer = webdriver.Chrome(desired_capabilities=desired_capabilities)
      broswer.get(url)
      ##### 设置睡眠时间,LOAD_TIME秒后执行停止加载的js命令 #####
      time.sleep(LOAD_TIME)
      broswer.execute_script("window.stop()")
      

      至此,加载完整页面缓慢的问题解决。

3.2,base64字符串的获取和格式转码
  • 问题1

    解析获取页面中的img标签的src属性值。src值其格式为:

    data:image/jpg;base64,base64字符串
    

    base64,后面的字符串才是要转码的内容,

  • 解决方案1

    直接使用python的str类split方法获取base64字符串:

    # img_src : 使用bs4获取的img标签的src属性值
    # base64str : 想要获取的base64字符串编码
    base64str = img_src.split('base64,')[1]
    
  • 问题2

    Base64编码需要转码为二进制编码后才能写为png或jpg文件,

  • 解决方案2

    使用base64类的转码函数转码:

    import base64
    
    # base64str 为获得的base64字符串
    imgdata = base64.b64decode(base64str)
    # imgdata 为转码后的二进制编码
    
    with open(img_name + ".jpg",'wb') as f:
            f.write(imgdata)
    
3.3,一些页面不存在
  • 问题

    测试过程中,发现一些图片编号的页面不存在,这样会导致获取页面标题失败,抛出异常,导致程序停止运行。

  • 解决方案

    通过if语句判断是否能够加载:

    title = soup.select('div[...] > div[class="title"]')
        if len(title) == 0:
            # 后续操作
    
3.4,部分照片无法加载
  • 问题

    一些照片无法加载,其对应的img标签src为加载时的标记图片的链接,无法正常解析,会导致抛出异常。

  • 解决方案

    尝试刷新页面,尝试若干次后直接放弃:

    具体会牵涉到较多逻辑,可以直接参考后面的参考代码的trytimes变量相关操作。

    大致逻辑:

    设置变量trytimes为页面刷新次数,初始值为0
    当判断出现图片加载失败时将其加一
    - 当某个页面爬取完成后将 trytimes 设为0
    - 当 trytimes 大于设置的尝试次数后直接跳到下一个页面爬取,放弃爬取无法加载的图片。(该页面爬取成功的图片还是会保存,再次加载的图片会保存或覆盖)
    

4,参考代码

这里直接吧代码贴出来吧,代码风格可能不太标准,欢迎指正:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import time
import base64
import os


def get_url(index):
    base_url = 'https://XXX/img/'	# 爬取的目标网站不变的部分
    url = base_url + str(index)		# 网站拼接
    return url 

def get_broswer():
    path = "C:\Program Files\...\chrome.exe"	# chrome 的可执行文件位置
    options = Options()
    # 无头浏览器效率更高
    # phantomjs 好像现在 selenium 已经不支持了
    # 只能使用Chrome浏览器的无头模式
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.binary_location = path

    # 关闭线程阻塞
    desired_capabilities = DesiredCapabilities.CHROME
    desired_capabilities["pageLoadStrategy"] = "none"
    broswer = webdriver.Chrome(chrome_options=options,executable_path='chromedriver.exe',desired_capabilities=desired_capabilities)
    return broswer

def get_content(broswer,url):
    broswer.get(url)
    time.sleep(5)
    broswer.execute_script("window.stop()")
    content = broswer.page_source
    return content

def get_imglist(content):
    soup = BeautifulSoup(content,'lxml')
    img_list = soup.select('div[class="..."] > img')
    return img_list

def get_base64(img):
    base64 = img.attrs.get('src')
    base64 = base64.split('base64,')
    if len(base64) == 1:
        return 'false'
    return base64[1]


def save_img(base64str,foldname,num):
    # 解码图片
    imgdata = base64.b64decode(base64str)
    img_fold = "./img/"+foldname+"/"
    # 每个图集保存一个文件夹,如果文件夹不存在要创建文件夹,否则会报错
    if os.path.exists(img_fold) == False:
        os.mkdir(img_fold)
    #将图片保存为文件
    with open(img_fold+str(num)+".jpg",'wb') as f:
        f.write(imgdata)

# 获取图集名称,方便命名文件夹
def get_title_name(cotent):
    soup = BeautifulSoup(content,'lxml')
    # 注意返回的是数组
    title = soup.select('div[class="content"] > div[class="title"]')
    if len(title) == 0:
        return 'false'
    title_name = title[0].get_text()
    return title_name

# 主函数
if __name__ == '__main__':
    broswer = get_broswer()

    #for index in range(100,4,-1):
    # 2304
    index = 1000    # 开始页    >大
    endpage = 1     # 结束页    >小 倒序爬取
    trytimes = 0	# 图片加载失败尝试次数
    dif = 1			# 爬取图集编号间隔,方便多开爬取,因为还没有学python多线程,就只想到这个法子,不在意速度这个可以不管
    while index >= endpage:
        flag = 0
        if trytimes > 2:
            print('TRYOVER_%d' % index)
            index -= dif
            trytimes = 0
            continue
           
        url = get_url(index)
        content = get_content(broswer,url)
        img_list = get_imglist(content)
        foldname = get_title_name(content)
        if foldname == 'false':
            trytimes += 1
            print('NOTFOUND_%d' % (index))
            continue
        for i in range(len(img_list)):
            img = img_list[i]  
            base64str = get_base64(img)       
            if base64str == 'false':
                # print('ERROLOAD_%d_%d' % (index,i))
                print('ERROLOAD_%d' % (index))
                flag = 1
                continue
            save_img(base64str,foldname,i+1)
        if flag == 0:
            print('SUCCESS_%d_%s √' % (index,foldname))
            index -= dif
            trytimes = 0
        if flag == 1:
            trytimes += 1
    print('OVER')        

总之最后效果还是比较理想的,最后成功爬取了想要的图片:

有关【爬虫】Selenium爬取动态网页的base64图片的更多相关文章

  1. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

  2. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  3. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

  4. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  5. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  6. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  7. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  8. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  9. RUBY - 网页抓取 - (OpenURI::HTTPError) - 2

    我正在尝试用ruby​​编写一个简单的网络抓取代码。它一直工作到第29个url,然后我收到此错误消息:C:/Ruby193/lib/ruby/1.9.1/open-uri.rb:346:in`open_http':500InternalServerError(OpenURI::HTTPError)fromC:/Ruby193/lib/ruby/1.9.1/open-uri.rb:775:in`buffer_open'fromC:/Ruby193/lib/ruby/1.9.1/open-uri.rb:203:in`blockinopen_loop'fromC:/Ruby193/lib/r

  10. ruby - 下载位置 Selenium-webdriver Cucumber Chrome - 2

    我将Cucumber与Ruby结合使用。通过Selenium-Webdriver在Chrome中运行测试时,我想将下载位置更改为测试文件夹而不是用户下载文件夹。我当前的chrome驱动程序是这样设置的:Capybara.default_driver=:seleniumCapybara.register_driver:seleniumdo|app|Capybara::Selenium::Driver.new(app,:browser=>:chrome,desired_capabilities:{'chromeOptions'=>{'args'=>%w{window-size=1920,1

随机推荐