Python爬虫零基础入门【第七章:动态页面入门(Playwright)·第2节】动态列表:滚动加载/点击翻页(通用套路)!

🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:

    • [🌟 开篇语](#🌟 开篇语)
      • [📚 上期回顾](#📚 上期回顾)
      • [🎯 本篇目标](#🎯 本篇目标)
      • [💡 两种别](#💡 两种别)
        • [模式 1:滚动加载(Infinite Scroll)](#模式 1:滚动加载(Infinite Scroll))
        • [模式 2:点击翻页(Click Pagination)](#模式 2:点击翻页(Click Pagination))
      • [🛠️ 实战 1:滚动加载采集](#🛠️ 实战 1:滚动加载采集)
      • [🛠️ 实战 2:点击翻页采集](#🛠️ 实战 2:点击翻页采集)
      • [⚠️ 新手常见坑](#⚠️ 新手常见坑)
        • [坑 1:滚动太快,数据没加载出来](#坑 1:滚动太快,数据没加载出来)
        • [坑 2:没有去重,数据重复采集](#坑 2:没有去重,数据重复采集)
        • [坑 3:点击后页面没变化](#坑 3:点击后页面没变化)
        • [坑 4:无限循环,采集不停](#坑 4:无限循环,采集不停)
      • [🚀 进阶技巧](#🚀 进阶技巧)
        • [技巧 1:智能等待(比 sleep 强)](#技巧 1:智能等待(比 sleep 强))
        • [技巧 2:模拟人类滚动(防反爬)](#技巧 2:模拟人类滚动(防反爬))
        • [技巧 3:截图调试翻页问题](#技巧 3:截图调试翻页问题)
      • [📊 完整示例:综合版爬虫](#📊 完整示例:综合版爬虫)
      • [📝 小结](#📝 小结)
      • [🎯 下期预告](#🎯 下期预告)
    • [🌟 文末](#🌟 文末)
      • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
      • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

📚 上期回顾

上一篇《Python爬虫零基础入门【第七章:动态页面入门(Playwright)·第1节】Playwright 第一次:打开页面、等待元素、拿到渲染后 HTML!》内容中,我们完成了 Playwright 初体验,学会了打开页面、等待加载、保存截图和 HTML。现在你能用自动化浏览器拿到渲染后的完整页面了------这是搞定动态网站的第一步。

但新问题来了:你打开商品列表,只看到前 20 条数据,往下滚才能加载更多;或者新闻网站需要点击"下一页"按钮才能翻页。这种交互式的数据加载,光 page.goto() 可搞不定。

今天,我们就来攻克动态列表采集的两大经典场景!

🎯 本篇目标

看完这篇,你能做到:

  1. 识别两种翻页模式(滚动加载 vs 点击翻页)
  2. 实现滚动翻页逻辑(判断加载完成、去重、终止条件)
  3. 实现点击翻页逻辑(等待元素、处理异常)
  4. 采集 200+ 条数据验收(能跑通就是真本事)

验收标准:用 Playwright 采集一个动态列表,至少拿到 200 条数据

💡 两种别

模式 1:滚动加载(Infinite Scroll)

特征:

  • 往下滚动,自动加载新内容
  • 没有明显的"下一页"按钮
  • 常见于社交媒体、电商、新闻 Feed

典型案例:

  • 微博首页
  • 小红书笔记列表
  • 淘宝商品搜索

判断方法:

json 复制代码
打开浏览器 → 滚动到底部 → 观察是否自动加载新内容
模式 2:点击翻页(Click Pagination)

特征:

  • 有明显的"下一页"、"加载更多"按钮
  • 点击后加载新数据
  • 页面可能刷新或局部更新

典型案例:

  • 某些论坛
  • 企业官网新闻列表
  • 部分电商(如京东早期版本)

判断方法:

json 复制代码
打开浏览器 → 寻找"下一页"或"加载更多"按钮 → 点击观察

🛠️ 实战 1:滚动加载采集

核心思路
json 复制代码
1. 打开页面
2. 等待初始数据加载
3. 提取当前可见的数据(去重)
4. 滚动到底部
5. 等待新数据加载(判断是否有新内容)
6. 重复步骤 3-5,直到没有新数据
代码实现
python 复制代码
from playwright.sync_api import sync_playwright
import time

class ScrollSpider:
    """滚动加载采集器"""
    
    def __init__(self, headless=True):
        self.headless = headless
        self.seen_items = set()  # 去重集合
    
    def scroll_and_collect(self, url, item_selector, max_scrolls=20):
        """
        滚动采集数据
        
        Args:
            url: 目标 URL
            item_selector: 列表项的 CSS 选择器
            max_scrolls: 最大滚动次数(防止死循环)
        
        Returns:
            list: 采集到的数据
        """
        items = []
        
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=self.headless)
            page = browser.new_page()
            
            print(f"🔍 正在打开:{url}")
            page.goto(url, wait_until='networkidle')
            
            # 等待初始数据加载
            print(f"⏳ 等待初始数据加载:{item_selector}")
            page.wait_for_selector(item_selector, timeout=10000)
            
            scroll_count = 0
            no_new_data_count = 0  # 连续无新数据次数
            
            while scroll_count < max_scrolls:
                scroll_count += 1
                print(f"\n📜 第 {scroll_count} 次滚动")
                
                # 提取当前页面的所有数据项
                elements = page.query_selector_all(item_selector)
                print(f"   当前页面共 {len(elements)} 个元素")
                
                # 提取并去重
                new_items_count = 0
                for elem in elements:
                    item_data = self.extract_item(elem)
                    if item_data and item_data['id'] not in self.seen_items:
                        items.append(item_data)
                        self.seen_items.add(item_data['id'])
                        new_items_count += 1
                
                print(f"   本轮新增:{new_items_count} 条(累计 {len(items)} 条)")
                
                # 判断是否还有新数据
                if new_items_count == 0:
                    no_new_data_count += 1
                    print(f"   ⚠️ 未发现新数据(连续 {no_new_data_count} 次)")
                    
                    if no_new_data_count >= 3:
                        print("🛑 连续 3 次无新数据,停止滚动")
                        break
                else:
                    no_new_data_count = 0  # 重置计数器
                
                # 滚动到底部
                previous_height = page.evaluate('document.body.scrollHeight')
                page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
                
                # 等待新内容加载
                time.sleep(2)  # 给页面一点反应时间
                
                # 检查页面高度是否变化(判断是否加载了新内容)
                new_height = page.evaluate('document.body.scrollHeight')
                if new_height == previous_height:
                    print("   ⚠️ 页面高度未变化,可能已到底部")
                    no_new_data_count += 1
                
            browser.close()
        
        print(f"\n✅ 采集完成!共获得 {len(items)} 条数据")
        return items
    
    def extract_item(self, element):
        """
        从元素中提取数据(需根据实际页面定制)
        
        Args:
            element: Playwright ElementHandle
        
        Returns:
            dict: 提取的数据
        """
        try:
            # 示例:提取标题和链接
            title_elem = element.query_selector('.title')
            link_elem = element.query_selector('a')
            
            if not title_elem or not link_elem:
                return None
            
            title = title_elem.inner_text().strip()
            href = link_elem.get_attribute('href')
            
            # 生成唯一 ID(用于去重)
            item_id = href  # 或者用 hash(title + href)
            
            return {
                'id': item_id,
                'title': title,
                'url': href
            }
        except Exception as e:
            print(f"   ⚠️ 提取失败:{e}")
            return None

# 使用示例
if __name__ == '__main__':
    spider = ScrollSpider(headless=True)
    
    items(
        url='https://example.com/products',
        item_selector='.product-item',  # 根据实际页面调整
        max_scrolls=20
    )
    
    # 保存结果
    import json
    with open('scrolled_items.json', 'w', encoding='utf-8') as f:
        json.dump(items, f, ensure_ascii=False, indent=2)

🛠️ 实战 2:点击翻页采集

核心思路
json 复制代码
1. 打开页面
2. 等待初始数据加载
3. 提取当前页数据
4. 查找"下一页"按钮
5. 点击按钮
6. 等待新页面加载
7. 重复步骤 3-6,直到没有下一页
代码实现
python 复制代码
class ClickPaginationSpider:
    """点击翻页采集器"""
    
    def __init__(self, headless=True):
        self.headless = headless
    
    def click_and_collect(self, url, item_selector, next_button_selector, max_pages=10):
        """
        点击翻页采集
        
        Args:
            url: 目标 URL
            item_selector: 列表项选择器
            next_button_selector: "下一页"按钮选择器
            max_pages: 最大采集页数
        
        Returns:
            list: 采集到的数据
        """
        items = []
        
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=self.headless)
            page = browser.new_page()
            
            print(f"🔍 正在打开:{url}")
            page.goto(url, wait_until='networkidle')
            
            page_num = 1
            
            while page_num <= max_pages:
                print(f"\n📄 正在采集第 {page_num} 页")
                
                # 等待列表加载
                try:
                    page.wait_for_selector(item_selector, timeout=10000)
                except Exception as e:
                    print(f"❌ 等待列表失败:{e}")
                    break
                
                # 提取当前页数据
                page_items = self.extract_page_items(page, item_selector)
                items.extend(page_items)
                print(f"   本页获得 {len(page_items)} 条数据(累计 {len(items)} 条)")
                
                # 查找"下一页"按钮
                next_button = page.query_selector(next_button_selector)
                
                if not next_button:
                    print("🛑 未找到"下一页"按钮,采集结束")
                    break
                
                # 检查按钮是否可点击(有些网站最后一页按钮会禁用)
                is_disabled = next_button.get_attribute('disabled')
                class_name = next_button.get_attribute('class') or ''
                
                if is_disabled or 'disabled' in class_name:
                    print("🛑 "下一页"按钮已禁用,已到最后一页")
                    break
                
                # 点击"下一页"
                print("   🖱️  点击"下一页"...")
                try:
                    # 方式 1:直接点击
                    next_button.click()
                    
                    # 等待页面更新(关键!)
                    time.sleep(2)  # 给点时间让页面反应
                    
                    # 方式 2(更稳定):等待 URL 变化或内容刷新
                    # page.wait_for_load_state('networkidle')
                    
                except Exception as e:
                    print(f"❌ 点击失败:{e}")
                    break
                
                page_num += 1
            
            browser.close()
        
        print(f"\n✅ 采集完成!共获得 {len(items)} 条数据")
        return items
    
    def extract_page_items(self, page, item_selector):
        """提取当前页所有数据"""
        items = []
        elements = page.query_selector_all(item_selector)
        
        for elem in elements:
            try:
                title = elem.query_selector('.title').inner_text().strip()
                link = elem.query_selector('a').get_attribute('href')
                
                items.append({
                    'title': title,
                    'url': link
                })
            except Exception as e:
                print(f"   ⚠️ 提取失败:{e}")
        
        return items

# 使用示例
if __name__ == '__main__':
    spider = ClickPaginationSpider(headless=True)
    
    items = spider.click_and_collect(
        url='https://example.com/news',
        item_selector='.news-item',
        next_button_selector='a.next-page',  # 或 'button:has-text("下一页")'
        max_pages=10
    )

⚠️ 新手常见坑

坑 1:滚动太快,数据没加载出来

现象:滚动后立即提取数据,发现没有新内容。

原因:Ajax 请求还在进行中,数据还没渲染。

解决:

python 复制代码
# ❌ 滚动后立即提取
page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
elements = page.query_selector_all(item_selector)

# ✅ 滚动后等待
page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
time.sleep(2)  # 等待加载

# 更稳定的方式:等待网络空闲
page.wait_for_load_state('networkidle')
elements = page.query_selector_all(item_selector)
坑 2:没有去重,数据重复采集

现象:采集到 500 条数据,实际只有 200 条不重复。

原因:滚动时,上一屏的数据还在页面上,会被重复提取。

解决:

python 复制代码
# 用集合去重
seen_ids = set()

for elem in elements:
    item_id = elem.get_attribute('data-id')  # 或用 URL、标题哈希
    
    if item_id in seen_ids:
        continue  # 跳过重复
    
    seen_ids.add(item_id)
    items.append(extract_data(elem))
坑 3:点击后页面没变化

现象:点击"下一页"后,还是老数据。

原因:

  1. 点击太快,页面还没更新
  2. 按钮是假的(纯展示,没有绑定事件)
  3. 点击被拦截(如广告弹窗遮挡)

解决:

python 复制代码
# 点击前滚动到按钮可见区域
next_button.scroll_into_view_if_needed()

# 点击后等待
next_button.click()
page.wait_for_load_state('networkidle')  # 等待网络空闲

# 或者等待 URL 变化
current_url = page.url
next_button.click()
page.wait_for_url(lambda url: url != current_url)
坑 4:无限循环,采集不停

现象:程序一直跑,永远停不下来。

原因:没有终止条件。

解决:

python 复制代码
# 设置最大次数
max_scrolls = 50

# 检测连续无新数据
no_new_data_count = 0
if new_items_count == 0:
    no_new_data_count += 1
    if no_new_data_count >= 3:
        break

# 检测页面高度不变
if new_height == previous_height:
    print("已到底部")
    break

🚀 进阶技巧

技巧 1:智能等待(比 sleep 强)
python 复制代码
# ❌ 硬编码等待
time.sleep(3)

# ✅ 等待特定元素数量变化
def wait_for_new_items(page, selector, current_count):
    """等待元素数量增加"""
    page.wait_for_function(
        f'document.querySelectorAll("{selector}").length > {current_count}',
        timeout=10000
    )

# 使用
current_count = len(page.query_selector_all(item_selector))
page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
wait_for_new_items(page, item_selector, current_count)
技巧 2:模拟人类滚动(防反爬)
python 复制代码
def smooth_scroll(page, distance=500):
    """平滑滚动(模拟人类行为)"""
    for i in range(0, distance, 50):
        page.evaluate(f'window.scrollBy(0, 50)')
        time.sleep(0.1)  # 每次滚动间隔 100ms

# 使用
smooth_scroll(page, distance=1000)
技巧 3:截图调试翻页问题
python 复制代码
# 每次翻页后截图
page.screenshot(path=f'page_{page_num}.png')
print(f"📸 第 {page_num} 页截图已保存,检查是否正常")

📊 完整示例:综合版爬虫

python 复制代码
class UniversalListSpider:
    """通用列表采集器(支持滚动和点击两种模式)"""
    
    def __init__(self, mode='scroll', headless=True):
        """
        Args:
            mode: 'scroll' 或 'click'
        """
        self.mode = mode
        self.headless = headless
        self.seen_ids = set()
    
    def crawl(self, config):
        """
        Args:
            config: {
                'url': '目标URL',
                'item_selector': '列表项选择器',
                'next_button': '下一页按钮选择器(仅 click 模式)',
                'max_items': 200  # 最多采集数量
            }
        """
        if self.mode == 'scroll':
            return self._scroll_mode(config)
        else:
            return self._click_mode(config)
    
    def _scroll_mode(self, config):
        # 实现滚动逻辑...
        pass
    
    def _click_mode(self, config):
        # 实现点击逻辑...
        pass

# 使用
spider = UniversalListSpider(mode='scroll')
items = spider.crawl({
    'url': 'https://example.com/list',
    'item_selector': '.item',
    'max_items': 200
})

📝 小结

今天我们学会了动态列表采集的两大套路

  1. 滚动加载(评估页面高度、去重、判断终止)
  2. 点击翻页(查找按钮、等待更新、处理异常)

记住核心原则:等待要充分、去重要严格、终止要明确。遇到问题多截图,90% 能看出加载到哪一步了。

🎯 下期预告

今天我们学会了用 Playwright 采集动态列表,但还有个终极问题:很多动态网站其实是可以直接抓接口的!

下一篇《优先 API:用 Network 找接口,回到 Requests(更稳定)》,我们会学习如何用浏览器开发者工具找到真正的数据接口,然后回到轻量级的 requests 采集------速度快 10 倍,稳定性也更高!🚀

验收作业:用 Playwright 采集一个动态列表(滚动或点击任选),拿到至少 200 条数据。把结果文件发我看看!加油!

🌟 文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。

相关推荐
火云洞红孩儿2 小时前
使用Python开发游戏角色识别!(游戏辅助工具开发入门)
人工智能·python·游戏
梦幻精灵_cq2 小时前
现代python捉虫记——f-string调试语法字面量解析坑点追踪(python版本3.12.11)
开发语言·python
新诺韦尔API2 小时前
手机空号检测接口技术对接常见问题汇总
大数据·开发语言·python·api
喵手2 小时前
Python爬虫零基础入门【第八章:项目实战演练·第1节】项目 1:RSS 聚合器(采集→去重→入库→查询)!
爬虫·python·rss·python爬虫实战·python爬虫工程化实战·python爬虫零基础入门·rss聚合器
沛沛老爹2 小时前
从Web到AI:多模态Agent Skills开发实战——JavaScript+Python全栈赋能视觉/语音能力
java·开发语言·javascript·人工智能·python·安全架构
storyseek2 小时前
RAG的四种的检索方式
python
一只大侠的侠2 小时前
用PyTorch Lightning快速搭建可复现实验 pipeline
人工智能·pytorch·python
偷星星的贼112 小时前
Python虚拟环境(venv)完全指南:隔离项目依赖
jvm·数据库·python
一株月见草哇2 小时前
[python/uv]现代化python工具[先占坑]
python·uv