You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Python requests与BeautifulSoup爬取解析大学课程先修要求信息

学校课程先修要求爬取解析方案

需求背景

我正在制定下学期的课程表,为提升效率,计划爬取学校官网页面提取所有课程及其先修要求,当前使用Python的requests和BeautifulSoup库实现。

问题描述

在提取必修先修课程列表和推荐先修课程列表时遇到问题,示例页面结构如下:

<div id="content">
    <!-- ... -->
    <p>
        <img src="gifs/triangle.jpg" width="5" height="10" alt="" border="0" /> Must: The courses
        <a href="DOMAIN/courses/12345.htm" target="_blank">Linear Algebra 101</a>,
        <a href="DOMAIN/courses/12346.htm" target="_blank">Calculus 101</a> (‏12346‎)‏<span class="heara"><a href="#remarks">2</a></span> and one of 
        <a href="DOMAIN/courses/12347.htm" target="_blank">Introduction to Statistics and Probability of Science</a>, <a href="DOMAIN/courses/11231.htm" target="_blank">Probability and Introduction to Computer Science Statistics</a>,
        <a href="DOMAIN/courses/12348.htm" target="_blank">Probability theory</a>. Recommended: <a href="DOMAIN/courses/12349‎.htm" target="_blank">Calculus 102</a> (‏12349‎)‏.
        <span class="heara"><a href="#remarks">3 </a></span>
    </p>
    <!-- ... -->
</div>

上述代码对应的文本内容为:

必修:课程线性代数101、微积分101(12346)2,以及科学统计与概率导论、计算机科学概率统计导论、概率论三者之一。推荐:微积分102(12349)。3

相关说明

  • 上述示例中,Linear Algebra 101和Calculus 101为必修先修,同时需从Introduction to Statistics and Probability of Science、Probability and Introduction to Computer Science Statistics、Probability theory三门课程中选一门修读。
  • 推荐先修课程为Calculus 102。
  • 部分课程名称自带编号,部分没有(如Calculus 101)。
  • 内容中存在#remarks类型的备注标识(即示例中的粗体数字2)。

预期输出

最终生成如下结构的字典:

{
  "must_courses": [
    "12345",
    "12346"
  ],
  "must_one_of": [
    [
      "12347",
      "11231",
      "12348"
    ]
  ],
  "recommended": [
    "12349"
  ]
}

现存难点

  • id为content的div下存在多个p标签,这些p标签没有class或id属性,出现位置不固定,通常是第2或第3个p标签,难以定位。
  • 部分课程名称不含课程编号,需要从<a href=$url属性中提取编号。
  • 需要识别内容的格式:开头可能是Must也可能是Recommended,还可能存在“and one of”类的多选一必选课程逻辑。

现有代码

目前已编写的代码如下:

soup = bs(r.content, 'html.parser')
content_div = soup.find('div', attrs={'id':'content'})
p_sections = content_div.find_all('p')
for index, p_section in enumerate(p_sections):
    if index == 3:
        # Sometimes its index 2 and sometimes its index 3
        # Tried to use: p_section.text

即便定位到了正确的p标签,也不知道如何解析其中的内容。

补充示例

示例1

页面结构:

<div id="content">
    <!-- ... -->
    <p>
        <img src="gifs/triangle.jpg" width="5" height="10" alt="" border="0" /> Must: The course <a href="DOMAIN/courses/12346.htm" target="_blank">Calculus 101</a> (‏12346‎)‏.
        <span class="heara"><a href="#remarks">2</a></span>
    </p>
    <!-- ... -->
</div>

对应文本内容:

必修:课程微积分101(12346)。2

示例2

页面结构:

<div id="content">
    <!-- ... -->
    <p><img src="gifs/triangle.jpg" width="5" height="10" alt="" border="0" />
 Recommended: The course <a href="DOMAIN/courses/04101.htm" target="_blank">Combinatorics</a>.</p>
    <!-- ... -->
</div>

对应文本内容:

推荐:课程组合数学。


可运行解析示例

import re
from bs4 import BeautifulSoup as bs

# 假设此处已经通过requests拿到了页面内容r
soup = bs(r.content, 'html.parser')
content_div = soup.find('div', attrs={'id':'content'})

# 初始化返回结果
result = {
    "must_courses": [],
    "must_one_of": [],
    "recommended": []
}

# 1. 定位目标p标签:不使用索引,通过关键词匹配定位
target_p = None
for p in content_div.find_all('p'):
    p_text = p.get_text(strip=True)
    if 'Must:' in p_text or 'Recommended:' in p_text:
        target_p = p
        break
if not target_p:
    # 没有找到先修要求,直接返回空结果
    print(result)
    exit()

# 2. 提前提取所有课程的编号映射,从href属性取编号,避免从名称取的问题
course_map = {}
for a_tag in target_p.find_all('a', href=True):
    href = a_tag['href']
    # 匹配课程链接里的编号
    cid_match = re.search(r'/courses/(\d+)\.htm', href)
    if cid_match:
        cid = cid_match.group(1)
        course_name = a_tag.get_text(strip=True)
        course_map[course_name] = cid

# 3. 拆分文本模块
full_text = target_p.get_text()
must_part = ""
one_of_part = ""
recommend_part = ""

# 拆分必修、多选一、推荐三个模块
if 'Must:' in full_text:
    after_must = full_text.split('Must:', 1)[1]
    if 'and one of' in after_must:
        must_part, after_one_of = after_must.split('and one of', 1)
        if 'Recommended:' in after_one_of:
            one_of_part, recommend_part = after_one_of.split('Recommended:', 1)
        else:
            one_of_part = after_one_of
    else:
        if 'Recommended:' in after_must:
            must_part, recommend_part = after_must.split('Recommended:', 1)
        else:
            must_part = after_must
elif 'Recommended:' in full_text:
    recommend_part = full_text.split('Recommended:', 1)[1]

# 4. 提取各模块课程编号
# 提取固定必修
for name, cid in course_map.items():
    if name in must_part and cid not in result['must_courses']:
        result['must_courses'].append(cid)

# 提取多选一必修
one_of_list = []
for name, cid in course_map.items():
    if name in one_of_part and cid not in one_of_list:
        one_of_list.append(cid)
if one_of_list:
    result['must_one_of'].append(one_of_list)

# 提取推荐课程
for name, cid in course_map.items():
    if name in recommend_part and cid not in result['recommended']:
        result['recommended'].append(cid)

print(result)

后续你可以根据实际页面的文本规则调整正则表达式和拆分逻辑即可适配更多场景。

内容的提问来源于stack exchange,提问作者vesii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 09:42:01