Python BeautifulSoup爬虫li标签提取失败与选择器用法咨询
Python爬虫
<li>结构数据提取问题排查 问题背景
使用Python开发网页爬虫时,无法从<li> _data_ </li>结构中提取数据,为此前提问的跟进场景:此前问题已获得解答,但未完全理解代码完整逻辑,本次需抓取2个站点的课程卡片全量信息,目标站点如下:
- https://premieragile.com/csm-training/
- https://www.simplilearn.com/agile-and-scrum/csm-certification-training
当前调试代码运行后输出为空,调试代码如下:
import requests import pandas as pd from bs4 import BeautifulSoup url = "https://premieragile.com/csm-training/" soup = BeautifulSoup(requests.get(url).content, "html.parser") all_data = [] for row in soup.select(".row > schedules-courses br-10 h-100 p-3 p-sm-4"): date = row.findAll(".d-flex align-items-center pb-4 h6").text.strip() # year = row.select_one(".li .batchDetails .date-details .date span").text.strip() # rating = row.select_one(".imdbRating").text.strip() # ...other variables all_data.append([date]) df = pd.DataFrame(all_data, columns=["date"]) print(df.head().to_markdown(index=False))
现存疑问
- for循环中如何正确编写div类对应的选择器?
- div、li、h系列标签、ul这类元素的DOM层级选择规则是什么?
当前仅掌握爬虫基础逻辑:创建空列表,通过BeautifulSoup解析页面对象后将提取数据存入列表,对DOM结构分析方法、数据列对应提取逻辑无清晰认知。
问题根因
当前代码空输出的核心原因是CSS选择器语法错误:
- CSS选择器中类名必须以
.开头,同个元素的多个类名需连续书写、中间不加空格。原代码中schedules-courses/br-10等类名未加.前缀,且类名间加了空格,会被识别为后代标签而非同元素类属性,无法匹配到任何DOM节点。 findAll()返回值是元素列表对象,不能直接调用.text属性,需取单个元素后再提取文本。
核心规则说明
1. CSS选择器基础语法
BeautifulSoup的select()/select_one()方法原生支持CSS选择器,常用规则如下:
- 标签选择:直接书写标签名即可,匹配所有对应标签,例如
div匹配所有div节点、li匹配所有列表项节点、h3匹配所有三级标题节点 - 类选择:类名前加
.前缀,同个节点的多个类名连续书写,例如<div class="card course red">对应的选择器为.card.course.red - ID选择:ID属性值前加
#前缀,例如<div id="course-list">对应的选择器为#course-list - 属性选择:通过
[属性名="属性值"]格式匹配,例如匹配data-id="100"的节点可写为[data-id="100"]
2. DOM层级匹配规则
- 后代匹配:选择器间加空格,匹配前序节点内部所有层级的符合条件的后代节点,例如
.card .title会匹配class为card的节点下,无论嵌套多少层的class为title的节点 - 直接子节点匹配:选择器间加
>,仅匹配前序节点直接下一层的符合条件的节点,跨层节点不会被匹配,例如.card > .title仅匹配card节点第一层子节点中class为title的节点
DOM分析实操流程
爬取任意页面卡片信息无需死记标签规则,按固定3步操作即可:
- 打开目标页面按F12唤起开发者工具,点击元素选择按钮,点击需要爬取的第一个卡片,在Elements面板定位到卡片的最外层DOM节点
- 提取该外层节点唯一、不与其他非卡片节点重复的类名/ID,编写for循环的选择器,保证循环遍历的每一个对象对应一个完整卡片
- 继续用元素选择工具点击卡片内需要提取的字段(日期、价格、讲师、链接等),定位到对应字段的DOM节点,在循环内部用
select_one()匹配单个节点,通过.text提取文本内容,通过["属性名"]提取链接、图片地址等属性值
注意:如果requests获取的页面源码中找不到目标卡片节点,说明内容为前端动态渲染,需替换为playwright等工具获取渲染后的完整源码,解析逻辑完全一致。
修正后可运行代码(适配第一个目标站点)
import requests import pandas as pd from bs4 import BeautifulSoup # 加请求头模拟浏览器访问,避免被站点反爬拦截 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } url = "https://premieragile.com/csm-training/" resp = requests.get(url, headers=headers) resp.raise_for_status() soup = BeautifulSoup(resp.content, "html.parser") all_data = [] # 匹配所有课程卡片外层节点 for card in soup.select(".schedules-courses.br-10"): card_info = {} # 提取课程日期,加判空避免节点不存在时报错 date_node = card.select_one(".date-details .date span") card_info["date"] = date_node.text.strip() if date_node else "" # 提取课程时间 time_node = card.select_one(".date-details .time") card_info["time"] = time_node.text.strip() if time_node else "" # 提取课程价格 price_node = card.select_one(".course-price") card_info["price"] = price_node.text.strip() if price_node else "" # 提取讲师姓名 trainer_node = card.select_one(".trainer-info .name") card_info["trainer"] = trainer_node.text.strip() if trainer_node else "" # 提取报名链接 enroll_node = card.select_one("a.enroll-btn") card_info["enroll_url"] = enroll_node["href"] if enroll_node else "" all_data.append(card_info) df = pd.DataFrame(all_data) print(df.head().to_markdown(index=False))
第二个站点的爬取逻辑完全相同,按照上述DOM分析流程替换对应选择器即可。
内容的提问来源于stack exchange,提问作者CodeLearner
相关产品推荐
相关产品推荐

