🔥本期内容已收录至专栏《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() 可搞不定。
今天,我们就来攻克动态列表采集的两大经典场景!
🎯 本篇目标
看完这篇,你能做到:
- 识别两种翻页模式(滚动加载 vs 点击翻页)
- 实现滚动翻页逻辑(判断加载完成、去重、终止条件)
- 实现点击翻页逻辑(等待元素、处理异常)
- 采集 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:点击后页面没变化
现象:点击"下一页"后,还是老数据。
原因:
- 点击太快,页面还没更新
- 按钮是假的(纯展示,没有绑定事件)
- 点击被拦截(如广告弹窗遮挡)
解决:
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
})
📝 小结
今天我们学会了动态列表采集的两大套路:
- 滚动加载(评估页面高度、去重、判断终止)
- 点击翻页(查找按钮、等待更新、处理异常)
记住核心原则:等待要充分、去重要严格、终止要明确。遇到问题多截图,90% 能看出加载到哪一步了。
🎯 下期预告
今天我们学会了用 Playwright 采集动态列表,但还有个终极问题:很多动态网站其实是可以直接抓接口的!
下一篇《优先 API:用 Network 找接口,回到 Requests(更稳定)》,我们会学习如何用浏览器开发者工具找到真正的数据接口,然后回到轻量级的 requests 采集------速度快 10 倍,稳定性也更高!🚀
验收作业:用 Playwright 采集一个动态列表(滚动或点击任选),拿到至少 200 条数据。把结果文件发我看看!加油!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。