Python爬取网页Flex渲染的动态内容

我最近使用Python爬取网页内容时遇到Flex渲染的动态页面,比如下图的课程目录标题,此时按鼠标右键,菜单里没有复制链接的选项。

我的目的是:获取各个视频标题、链接。

按F12进入开发者模式分析网页,可见有多个flex标签,像这种通过flex动态渲染的网页,视频链接隐藏在JS代码里,需要人工点击才能运算出正确的链接,普通的requests库的get是无法直接获取的。

于是改变思路,尝试selenium的webdriver来打开浏览器,打开该网页,然后用find_element的By来搜索关键词“视频”,看看能不能定位到“视频”的元素:

from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
# 关掉密码弹窗
options.add_experimental_option("prefs", prefs)
# 关闭提示“您的连接不是私密连接”
options.add_argument("--ignore-certificate-errors")
# 关闭提示“Chrome受自动控制提示”
options.add_experimental_option('useAutomationExtension', False) 
# 关闭提示“Chrome受自动控制提示”
options.add_experimental_option('excludeSwitches', ['enable-automation']) 
options.add_argument("--disable-extensions")
driver = webdriver.Chrome(options=options)

driver.get('......') # 打开网页url

l1=driver.find_element(By.PARTIAL_LINK_TEXT,'视频')
l2=driver.find_element(By.LINK_TEXT,'视频')

结果无论是l1还是l2,都会报错。

再尝试别的办法,如selenium的locate with:

from selenium.webdriver.support.relative_locator import locate_with, with_tag_name

l3=locate_with(By.LINK_TEXT, '视频')
l3.click()

l3=locate_with(...) 这一行通过了,但下一句l3.click()报错,提示没有click()的属性。

再想办法,改为:

l4=driver.find_element(l3)
l4.click()

但同样报错:selenium.common.exceptions.NoSuchElementException: Message: Cannot locate relative element with: {'link text': '视频'}

上面都使用了By.LINK_TEXT查找关键词来定位,是希望能精确定位,但在Flex渲染的页面里定位不了。

那么find_element改用By.CLASS_NAME又行不行?

l5=driver.find_element(By.CLASS_NAME,'item-title')
print(l5.text)

好!这下没有报错。结果返回l5的值是字符串:'一张图了解技术指标(上)'

接下来发送点击命令,如果顺利的话就通过driver.current_url获取播放视频页面的链接。

使用while 1循环,遍历查找所有class为“item-title”,直至查找报错,跳出循环。

links=[]
# 参考来源:https://blog.csdn.net/saber_sss/article/details/103460706
new_location=WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME,'item-title')))

while 1:    
    try:
        # 查询窗口总数,返回一个包含所有窗口句柄handles的列表
        handles=driver.window_handles 
        title=new_location.text # 获取视频标题
        new_location.click()
        # 对比一开始获取的窗口总数,确认新窗口出现了再去切换
        WebDriverWait(driver,5).until(EC.new_window_is_opened(handles))
        # 切换到新窗口
        handles=driver.window_handles #再次获取窗口句柄handles
        #执行切换窗口操作
        driver.switch_to.window(handles[-1])
        links.append([title, driver.current_url])
        # 关闭当前窗口
        driver.close()
        # 记得还要再切换去原来的窗口
        driver.switch_to.window(handles[0])
        last_location=new_location
        # 查找下一行
        new_location=driver.find_element(locate_with(By.CLASS_NAME,'item-title').below(last_location))
    except Exception:
        break

for i in links: print(i)

运行结果:

貌似成功了。但是对比原网页上的视频标题,获取的结果少了一半,find_element代码每次都隔行获取,为什么会隔行?离得太近了吗?

后来我到selenium的官网文档里查看关于locators的用法,发现除了below是查找下一行之外,还有“near”查找,于是把below改为near,结果却是查找到视频标题的第一行、第二行、第一行、第二行。。。如此循环。

为解决这个问题,只能先后处理查找奇数行、偶数行,最后合并奇、偶行结果并重新排序。

# 初始化webdriver的过程不写了

links = []
l1 = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'item-title')))
l2 = driver.find_element(locate_with(By.CLASS_NAME,'item-title').near(l1))
index = 0
for locator in (l1,l2):
    while 1:    
        try:
            # 查询窗口总数,返回一个包含所有窗口句柄handles的列表
            handles = driver.window_handles 
            title = locator.text
            locator.click()
            # 对比一开始获取的窗口总数,确认新窗口出现了再去切换
            WebDriverWait(driver, 5).until(EC.new_window_is_opened(handles))
            # 再次获取窗口句柄handles
            handles = driver.window_handles 
            # 新老句柄列表可以看出,新出现的句柄在列表里面排在后面
            # 执行切换窗口操作
            driver.switch_to.window(handles[-1])
            print(index, title, driver.current_url)
            links.append([index, title, driver.current_url])
            driver.close()
            # 记得还要切换回原来的窗口
            driver.switch_to.window(handles[0])
            locator = driver.find_element(locate_with(By.CLASS_NAME,'item-title').below(locator))
            # 为解决隔行,index加2
            index+=2
        except Exception:
            # 查找不到再多的元素就退出循环
            break
    # 然后处理偶数行,index设为1
    index = 1
# 把结果重新排序
links = sorted(links)
for i in links: print(i)
driver.quit()

运行结果截图:

很不错!但还有一个bug:运行结果的第2、3项重复,估计是selenium的定位元素存在误差造成的。

2023年4月13日更新:简单而优雅的写法

今天更新一下简单一点又没有上述 bug 的方法。先来看第一幅图:

之前的笨方法是先定位 class="item-title",由于有很多个相同的 class 元素,导致Selenium定位不准确。

简单而优雅的方法是:先定位 class="item-title" 的上一级 class="column_catalog_item_wrap",然后从这个元素开始往下循环遍历定位 class="item-title",再进行点击链接、获取链接。

下面是优化后的代码:

# 初始化webdriver的过程不写了

links = []
l1 = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'column_catalog_item_wrap')))
l2 = l1.find_elements(By.CLASS_NAME, 'item-title')

for index, locator in enumerate(l2):
    # 查询窗口总数,返回一个包含所有窗口句柄handles的列表
    handles=driver.window_handles 
    title=locator.text
    locator.click()
    # 对比一开始获取的窗口总数,确认新窗口出现了再去切换
    WebDriverWait(driver,5).until(EC.new_window_is_opened(handles))
    # 再次获取窗口句柄handles
    handles=driver.window_handles 
    # 新老句柄列表可以看出,新出现的句柄在列表里面排在后面
    # 执行切换窗口操作
    driver.switch_to.window(handles[-1])
    print(index, title, driver.current_url)
    links.append([index, title, driver.current_url])
    driver.close()
    # 记得还要切换回原来的窗口
    driver.switch_to.window(handles[0])
    
for i in links: print(i)
driver.quit()

参考来源:

【1】 爬虫实例(5)网页动态内容的识别_网页内容分类识别_演技拉满的白马的博客-CSDN博客

【2】浅谈selenium4新增功能之相对定位_the-ruffian的博客-CSDN博客_selenium相对定位法

【3】 python+selenium之窗口切换三种操作_saber_sss的博客-CSDN博客_python wd 切换窗口

【4】 Locator strategies | Selenium

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Selenium 中切换到另一个 frame 或 iframe 中,可以使用 `switch_to.frame()` 方法。如果要切换到内嵌的 frame 中,需要先切换到包含该 frame 的父级 frame 中,再在父级 frame 中切换到目标 frame。以下是示例代码: ```python from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() # 打开网页 driver.get("http://example.com") # 等待 frame 加载完成 frame = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, "iframe"))) # 切换到包含 frame 的父级 frame 中 driver.switch_to.frame(frame) # 切换到目标 frame 中 subframe = driver.find_element_by_tag_name("iframe") driver.switch_to.frame(subframe) # 在目标 frame 中进行操作,例如查找元素 element = driver.find_element_by_css_selector(".flex-container") # 切换回上一级 frame driver.switch_to.parent_frame() # 切换回默认 content driver.switch_to.default_content() # 关闭浏览器 driver.quit() ``` 在上面的代码中,首先通过 `WebDriverWait` 等待页面中的 frame 加载完成,然后使用 `switch_to.frame()` 方法切换到包含 frame 的父级 frame 中,再在父级 frame 中使用 `switch_to.frame()` 方法切换到目标 frame 中。在目标 frame 中进行操作后,可以使用 `switch_to.parent_frame()` 方法切换回上一级 frame,或使用 `switch_to.default_content()` 方法切换回默认 content。最后,记得关闭浏览器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值