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

文章讲述了在爬取使用Flex渲染的动态网页时,如何利用Selenium的webdriver和find_element方法来定位并获取视频链接。作者通过尝试不同的定位策略,如By.LINK_TEXT、By.CLASS_NAME,以及使用relative_locator,最终找到了通过定位上级元素然后遍历子元素的方法,成功避免了定位误差和重复的问题,实现了动态内容的抓取和窗口切换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我最近使用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

<think>好的,用户让我帮忙用Python写一个河源旅游推荐网站。首先,我需要理解他的需求。他可能想要一个展示河源旅游景点的网站,可能包括景点介绍、推荐功能、用户交互等。作为开发者,我需要考虑技术选型、功能模块、数据源以及可能的扩展性。 首先,技术选型。Python常用的Web框架有Django和Flask。Django比较全面,自带ORM、模板引擎,适合快速开发;Flask更轻量,灵活。用户可能希望快速搭建,所以Django可能更合适。不过如果用户想更简单,可能用Flask也可以。这里可能需要进一步询问,但假设用户希望简单明了,先选Flask。 接下来是功能模块。基本的旅游推荐网站需要景点展示、分类筛选、搜索功能、推荐算法。用户可能还需要用户注册登录、评论收藏等功能,但可能初期不需要太复杂。先做基础部分:首页展示景点列表,分类(比如自然景观、文化古迹),推荐页面,详情页。推荐算法可以基于简单的标签匹配或者热门推荐,后期再考虑更复杂的算法。 数据源方面,需要景点数据,包括名称、描述、图片、类型、位置等。可能需要手动录入,或者爬取相关网站的数据,但考虑到合法性,先建议用户手动创建示例数据。 然后考虑前端展示。使用HTML模板,结合CSS框架如Bootstrap快速布局。可能还需要一些图片资源,用户可能需要自己准备或使用占位图。 数据库方面,Flask通常搭配SQLAlchemy,使用SQLite作为开发数据库,方便快捷。创建景点表,包含必要字段。 推荐算法部分,初期可以按类型推荐,比如用户查看某个景点,推荐同类型的其他景点。或者根据热门程度排序。后期可以加入用户行为分析,但初期保持简单。 接下来,我需要规划项目结构:app.py作为入口,templates存放HTML模板,static放CSS和图片,数据库模型定义等。然后分步骤实现路由、视图函数、模板渲染、数据库交互。 可能需要考虑的问题:如何展示景点数据?是否需要分页?搜索功能如何实现?推荐逻辑的具体实现方式。以及如何部署,但用户可能暂时只需要本地运行,所以先提供开发服务器启动方式。 最后,测试代码,确保各个页面能正常访问,数据正确显示,推荐功能有效。同时,提供扩展建议,比如添加用户系统、评论、更智能的推荐算法等,供用户参考。 总结下来,步骤是:安装Flask,创建项目结构,定义数据库模型,编写视图函数和路由,制作模板,添加简单推荐逻辑,测试运行。然后给用户示例代码和解释,说明如何扩展功能。</think>以下是使用Python Flask框架快速搭建河源旅游推荐网站的步骤和示例代码: ### 一、技术方案 1. **框架选择**:Flask(轻量级Web框架) 2. **数据库**:SQLite + SQLAlchemy 3. **前端**:Bootstrap5 + Jinja2模板 4. **推荐逻辑**:基于标签的简单推荐 5. **功能模块**: - 首页推荐 - 景点分类 - 详情页面 - 搜索功能 - 简易推荐系统 ### 二、项目结构 ```bash heyuantravel/ ├── app.py ├── config.py ├── models.py ├── static/ │ └── css/ └── templates/ ├── index.html ├── detail.html └── search.html ``` ### 三、核心代码实现 #### 1. 数据库模型(models.py) ```python from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class Attraction(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text) location = db.Column(db.String(100)) category = db.Column(db.String(50)) # 自然/文化/美食等 image = db.Column(db.String(200)) tags = db.Column(db.String(200)) # 逗号分隔的标签 def similar_attractions(self): # 简易推荐:相同标签的景点 return Attraction.query.filter( Attraction.tags.contains(self.tags.split(',')[0]), Attraction.id != self.id ).limit(4).all() ``` #### 2. Flask应用(app.py) ```python from flask import Flask, render_template, request from models import db, Attraction app = Flask(__name__) app.config.from_pyfile('config.py') db.init_app(app) @app.route('/') def index(): # 首页推荐:精选+热门 featured = Attraction.query.filter_by(category='自然').limit(4).all() popular = Attraction.query.order_by(Attraction.id.desc()).limit(8).all() return render_template('index.html', featured=featured, popular=popular) @app.route('/attraction/<int:attraction_id>') def detail(attraction_id): attraction = Attraction.query.get_or_404(attraction_id) return render_template('detail.html', attraction=attraction, similar=attraction.similar_attractions()) @app.route('/search') def search(): keyword = request.args.get('q', '') results = Attraction.query.filter( Attraction.name.contains(keyword) | Attraction.description.contains(keyword) ).all() return render_template('search.html', results=results, keyword=keyword) if __name__ == '__main__': with app.app_context(): db.create_all() app.run(debug=True) ``` #### 3. 配置文件(config.py) ```python import os BASE_DIR = os.path.abspath(os.path.dirname(__file__)) SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'data.db') SECRET_KEY = 'your_secret_key_here' ``` ### 四、前端示例(templates/index.html) ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>河源旅游推荐</title> <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <!-- 导航栏 --> <nav class="navbar bg-light"> <div class="container"> <a class="navbar-brand" href="/">河源旅游</a> <form class="d-flex" action="/search"> <input class="form-control" type="search" name="q" placeholder="搜索景点..."> </form> </div> </nav> <!-- 推荐景区 --> <section class="container my-5"> <h2 class="mb-4">精选推荐</h2> <div class="row g-4"> {% for item in featured %} <div class="col-md-3"> <div class="card h-100"> <img src="{{ item.image }}" class="card-img-top"> <div class="card-body"> <h5>{{ item.name }}</h5> <p class="text-muted">{{ item.location }}</p> <a href="/attraction/{{ item.id }}" class="btn btn-primary">查看详情</a> </div> </div> </div> {% endfor %} </div> </section> </body> </html> ``` ### 五、运行步骤 1. 安装依赖: ```bash pip install flask flask-sqlalchemy ``` 2. 初始化数据库(Python Shell): ```python from app import app from models import db, Attraction with app.app_context(): db.create_all() # 添加示例数据 db.session.add(Attraction( name="万绿湖", category="自然", tags="湖泊,生态,摄影", description="华南第一大湖...", image="/static/images/wanlvhu.jpg" )) db.session.commit() ``` 3. 启动服务: ```bash python app.py ``` ### 六、扩展建议 1. **数据增强**: - 添加真实景点数据(建议20+条) - 对接地图API显示位置 - 添加用户评分系统 2. **推荐算法升级**: ```python # 示例:协同过滤推荐 def get_recommendations(): # 实现基于用户行为的推荐逻辑 pass ``` 3. **高级功能**: - 用户登录系统 - 收藏功能 - 旅游路线规划 - 天气信息集成 4. **部署方案**: - 使用Nginx + Gunicorn部署 - 数据库迁移至MySQL/PostgreSQL - 添加缓存机制(Redis) 这个基础版本可以在本地运行并展示河源旅游景点信息,后续可以根据需求逐步扩展功能。需要完整代码或具体功能实现细节可以告诉我,我可以提供更详细的指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值