数据采集第一次博客作业

数据采集第一次博客作业

本次作业使用 Python 语言,综合运用 requests 网络请求库、BeautifulSoup 解析库以及 re 正则表达式,完成三个定向爬虫任务:软科中国大学排名爬取、当当网"书包"商品比价爬取、福州大学新闻网图片批量下载。


作业①:软科中国大学排名定向爬取

1)、实验过程

任务要求:用 requests 和 BeautifulSoup 库定向爬取给定网址 http://www.shanghairanking.cn/rankings/bcur/2020 的数据,屏幕打印爬取的大学排名信息(排名、学校名称、省市、学校类型、总分)。

思路分析:打开目标页面后,按 F12 查看网页源代码,发现排名数据直接写在 HTML 表格中。每所大学对应一个 <tr> 行,行内有多个 <td> 单元格,分别存放排名、学校名称、省市、学校类型和总分。因此用 requests 获取网页后,直接用 BeautifulSoup 定位 <tbody> 下的每一行 <tr>,再依次取出各单元格内容。

网页结构如下(通过 F12 开发者工具分析得出):

<tbody>
  <tr>
    <td><div class="ranking top1">1</div></td>          <!-- 排名 -->
    <td class="align-left">
      <div class="univname">
        <span class="name-cn">清华大学</span>             <!-- 学校名称 -->
      </div>
    </td>
    <td>北京</td>                                        <!-- 省市 -->
    <td>综合</td>                                        <!-- 学校类型 -->
    <td>852.5</td>                                      <!-- 总分 -->
  </tr>
</tbody>

完整代码:

# -*- coding: utf-8 -*-
"""
作业①:定向爬取软科中国大学排名(BCUR 2020)
目标网址:http://www.shanghairanking.cn/rankings/bcur/2020
技术栈:requests + BeautifulSoup
输出列:排名 | 学校名称 | 省市 | 学校类型 | 总分
"""

import requests
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://www.shanghairanking.cn/rankings/bcur/2020"


def fetch_html(url):
    """请求网页,返回解析后的 BeautifulSoup 对象。"""
    resp = requests.get(url, headers=HEADERS, timeout=15)
    resp.raise_for_status()
    resp.encoding = resp.apparent_encoding  # 自动识别编码,防止中文乱码
    return BeautifulSoup(resp.text, "html.parser")


def parse_rankings(soup):
    """从页面中解析所有大学排名信息,返回列表。"""
    rankings = []
    tbody = soup.find("tbody")           # 定位表格主体
    for tr in tbody.find_all("tr"):     # 遍历每一行
        tds = tr.find_all("td")         # 取出这一行的所有单元格
        rank = tds[0].get_text(strip=True)                          # 排名
        name_cn = tds[1].find("span", class_="name-cn").get_text(strip=True)  # 学校名称
        province = tds[2].get_text(strip=True)                      # 省市
        category = tds[3].get_text(strip=True)                      # 学校类型
        score = tds[4].get_text(strip=True)                         # 总分
        rankings.append({
            "排名": rank, "学校名称": name_cn, "省市": province,
            "学校类型": category, "总分": score,
        })
    return rankings


def print_table(rankings, top_n=20):
    """按格式打印前 top_n 条排名信息。"""
    print("{:<6}{:<14}{:<8}{:<8}{:<8}".format(
        "排名", "学校名称", "省市", "学校类型", "总分"))
    print("-" * 46)
    for row in rankings[:top_n]:
        print("{:<6}{:<14}{:<8}{:<8}{:<8}".format(
            row["排名"], row["学校名称"], row["省市"],
            row["学校类型"], row["总分"]))


def main():
    soup = fetch_html(URL)
    rankings = parse_rankings(soup)
    print_table(rankings)


if __name__ == "__main__":
    main()

运行结果:

排名    学校名称          省市      学校类型    总分
----------------------------------------------
1     清华大学          北京      综合      852.5
2     北京大学          北京      综合      746.7
3     浙江大学          浙江      综合      649.2
4     上海交通大学        上海      综合      625.9
5     南京大学          江苏      综合      566.1
6     复旦大学          上海      综合      556.7
7     中国科学技术大学      安徽      理工      526.4
8     华中科技大学        湖北      综合      497.7
9     武汉大学          湖北      综合      488.0
10    中山大学          广东      综合      457.2
11    西安交通大学        陕西      综合      452.5
12    哈尔滨工业大学       黑龙江     理工      450.2
13    北京航空航天大学      北京      理工      445.1
14    北京师范大学        北京      师范      440.9
15    同济大学          上海      理工      439.0
16    四川大学          四川      综合      435.7
17    东南大学          江苏      综合      432.7
18    中国人民大学        北京      综合      409.7
19    南开大学          天津      综合      402.1
20    北京理工大学        北京      理工      395.6

work1_run

打开浏览器

work1_browser

找到tbody下拥有所需信息
work1_tbody

2)、心得体会

第一次作业让我真正学会了"先分析网页结构,再写爬虫代码"这个核心思路。一开始我拿到网址就直接写代码,结果在 HTML 里怎么也找不到学校名称。后来按 F12 仔细看开发者工具,发现数据其实就在 <tbody> 表格里,每所大学是一个 <tr>,每个字段对应一个 <td>。这个发现让我明白:写爬虫之前一定要先搞清楚数据在网页里的位置,不能上来就盲写。

通过这次练习,我掌握了 BeautifulSoup 的基本用法:find() 定位单个标签、find_all() 遍历同类标签、get_text(strip=True) 提取标签内文字。同时也理解了请求头中 User-Agent 的作用——不加的话服务器会认出你是 Python 脚本并拒绝访问。另外 resp.encoding = resp.apparent_encoding 这一行很重要,它让程序自动识别网页编码,避免中文乱码。


作业②:当当网"书包"商品比价爬虫

1)、实验过程

任务要求:用 requests 和 re 库设计某商城商品比价定向爬虫,以关键词"书包"搜索,爬取商品名称和价格(输出序号、价格、商品名)。我选择的商城是当当网(search.dangdang.com)。

思路分析:用浏览器打开当当网"书包"搜索页,查看页面源码发现每个商品块的结构非常规整:价格在 <span class="price_n">&yen;价格</span> 中,商品名在紧随其后的 <p class="name"><a title="商品名"> 中。由于价格标签恰好出现在商品名标签之前,可以用一条正则表达式同时把"价格"和"商品名"配对抓取。

完整代码:

# -*- coding: utf-8 -*-
"""
作业②:商城商品比价定向爬虫(当当网 search.dangdang.com)
关键词:书包
技术栈:requests + re(正则表达式)
输出列:序号 | 价格 | 商品名
"""

import re
import requests

# 请求头:模拟浏览器访问
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",
}

# 当当网"书包"搜索结果页(key 为 GBK 编码后的"书包")
SEARCH_URL = "http://search.dangdang.com/?key=%CA%E9%B0%FC&act=input"

# 正则:同时匹配价格(¥后的数字)与商品名(title属性),二者在同一商品块内相邻
PATTERN = re.compile(
    r'<p class="price"[^>]*>\s*<span class="price_n">&yen;([0-9.]+)</span></p>'
    r'.*?<p class="name"[^>]*>\s*<a[^>]*title="([^"]+)"',
    re.S  # 让 . 匹配换行符
)


def fetch_page(url):
    """请求搜索页,返回网页文本(指定 GBK 编码避免中文乱码)。"""
    resp = requests.get(url, headers=HEADERS, timeout=15)
    resp.raise_for_status()
    resp.encoding = "gbk"  # 当当网页面编码为 GBK
    return resp.text


def parse_products(html):
    """用正则从网页中提取 (价格, 商品名) 列表。"""
    items = PATTERN.findall(html)  # 返回所有匹配的 (价格, 商品名) 元组
    result = []
    seen = set()
    for price, name in items:
        name = name.strip()
        if name and name not in seen:  # 去重
            seen.add(name)
            result.append((price, name))
    return result


def print_table(products, top_n=15):
    """按格式打印前 top_n 条商品信息。"""
    print("{:<6}{:<10}{}".format("序号", "价格", "商品名"))
    print("-" * 70)
    for i, (price, name) in enumerate(products[:top_n], start=1):
        short_name = name if len(name) <= 40 else name[:40] + "..."
        print("{:<6}{:<10}{}".format(i, price, short_name))
    print("-" * 70)
    print("共抓取到 {} 件相关商品(仅显示前 {} 件)。".format(
        len(products), min(top_n, len(products))))


def main():
    html = fetch_page(SEARCH_URL)
    products = parse_products(html)
    print_table(products)


if __name__ == "__main__":
    main()

运行结果:

序号    价格        商品名
----------------------------------------------------------------------
1     146.00    迪士尼书包小学生女童一三六年级公主女孩轻便儿童护脊减负双肩包邮1
2     89.99     书包女新款初中生高中学生大容量轻便减负护脊简约学院风双肩包
3     39.99     书包幼儿园女孩 潮小兔子书包3-6岁大中小班男童儿童卡通可爱韩版
4     224.00    儿童书包护脊减负大容量2026新款男生一二三到六年级男孩超轻书包
5     99.80     书包男女生 1-6年级大容量新款小学生耐磨透气书包儿童双肩包...
6     75.80     软书包女孩小学生一二三到六年级可爱卡通超轻便减负儿童双肩背包
7     132.00    迪士尼幼儿园书包大班女童流沙卡通可爱女孩学前班儿童宝宝双肩包
8     119.00    书包男女生 小学生书包男孩女孩一二三四五六年级初中生大容量轻便休闲双肩包
9     239.00    瑞牌小学生书包男生减负护脊轻便儿童书包四到六年级女生高颜值书包新款...
10    93.50     书包女初中生轻便小学生可爱原创三到六年级大容量双肩背包
11    62.82     书包小学生女生大容量儿童护脊女孩三到六新款一年级轻便男童
12    59.99     小学生书包女孩轻便小清新高颜值可爱1-3-6年级双肩包耐脏背包
13    99.00     书包男女生 小学生男女6-14岁男女童大容量双肩背包轻便休闲儿童书包...
14    62.00     双肩包女新款牛津布小背包韩版潮女士尼龙百搭时尚帆布书包包
15    69.00     双肩包女士2018新款韩版百搭潮背包包软皮休闲时尚旅行大容量书包
----------------------------------------------------------------------
共抓取到 60 件相关商品(仅显示前 15 件)。

work2_web

先找网页

work2_format

找到格式,在网页上右键 →"查看网页源代码"。这次能搜到商品名,说明数据直接写在 HTML 里,不需要找接口。
work2_source

2)、心得体会

这个任务让我真正练习了用正则表达式从非结构化 HTML 中提取信息。一开始我尝试爬京东,但京东有较强的反爬机制,直接请求只返回了一个验证页面;换用当当网后顺利拿到了数据,这让我明白"选对目标网站"本身就是爬虫实践的一部分。在编写正则时,我遇到两个坑:一是当当网页面是 GBK 编码,如果不显式指定 resp.encoding = "gbk",中文会变成乱码;二是商品名和价格必须按"块"配对,不能分别用两条正则各抓一列再按下标拼起来,否则一旦某条商品缺字段就会错位。最后通过把价格标签和商品名标签写进同一条正则、用分组 (...) 一次性捕获两个元组,再配合 set 去重,保证了结果的整齐。


作业③:福州大学新闻网图片批量爬取

1)、实验过程

任务要求:爬取给定网页 https://news.fzu.edu.cn/yxfd.htm(或自选网页)中的所有 JPEG、JPG、PNG 格式图片文件,统一保存到一个文件夹中。

思路分析:用 requests 获取页面 HTML,用 BeautifulSoup 的 find_all("img") 找到所有 <img> 标签,取出 src 属性;再用正则筛选后缀为 .jpg/.jpeg/.png 的地址,通过 urljoin 把相对路径补全为绝对 URL,最后用二进制方式逐张下载并写入本地 fzu_images 文件夹。

完整代码:

# -*- coding: utf-8 -*-
"""
作业③:定向爬取网页中所有 JPEG/JPG/PNG 图片
目标网址:https://news.fzu.edu.cn/yxfd.htm (福州大学新闻网-院系风采)
技术栈:requests + BeautifulSoup
输出:将页面中所有 .jpg/.jpeg/.png 图片保存到本地 images 文件夹
"""

import os
import re
from urllib.parse import urljoin
import requests
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",
}

PAGE_URL = "https://news.fzu.edu.cn/yxfd.htm"
SAVE_DIR = "fzu_images"
IMG_SUFFIX = re.compile(r"\.(jpe?g|png)$", re.I)  # 只下载 jpg/jpeg/png


def fetch_page(url):
    """请求目标网页并返回响应对象。"""
    resp = requests.get(url, headers=HEADERS, timeout=15)
    resp.raise_for_status()
    resp.encoding = resp.apparent_encoding
    return resp


def collect_image_urls(html, base_url):
    """用 BeautifulSoup 解析页面,收集满足后缀要求的图片绝对地址。"""
    soup = BeautifulSoup(html, "html.parser")
    img_urls = []
    for img in soup.find_all("img"):
        src = img.get("src")
        if not src:
            continue
        if IMG_SUFFIX.search(src):            # 后缀筛选
            abs_url = urljoin(base_url, src)  # 相对地址 -> 绝对地址
            if abs_url not in img_urls:       # 去重
                img_urls.append(abs_url)
    return img_urls


def download_images(img_urls, save_dir):
    """下载图片列表到指定文件夹,返回成功下载的数量。"""
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)  # 不存在则创建文件夹
    count = 0
    for i, img_url in enumerate(img_urls, start=1):
        try:
            resp = requests.get(img_url, headers=HEADERS, timeout=15)
            resp.raise_for_status()
            suffix = os.path.splitext(img_url)[1].split("?")[0]
            filename = os.path.join(save_dir, "img_{:03d}{}".format(i, suffix))
            with open(filename, "wb") as f:
                f.write(resp.content)  # 二进制写入图片
            count += 1
            print("已下载:{} -> {}".format(img_url, filename))
        except Exception as e:
            print("下载失败:{},原因:{}".format(img_url, e))
    return count


def main():
    resp = fetch_page(PAGE_URL)
    img_urls = collect_image_urls(resp.text, PAGE_URL)
    print("页面中符合要求的图片共 {} 张,开始下载……".format(len(img_urls)))
    n = download_images(img_urls, SAVE_DIR)
    print("=" * 50)
    print("下载完成!成功保存 {}/{} 张图片到文件夹:{}".format(
        n, len(img_urls), os.path.abspath(SAVE_DIR)))


if __name__ == "__main__":
    main()

运行结果:

页面中符合要求的图片共 19 张,开始下载……
已下载:https://news.fzu.edu.cn/images/top_search.png -> fzu_images\img_001.png
已下载:https://news.fzu.edu.cn/images/logo.png -> fzu_images\img_002.png
已下载:https://news.fzu.edu.cn/images/footer_logo.png -> fzu_images\img_003.png
……(共 19 行)
已下载:https://news.fzu.edu.cn/__local/3/76/72/...198.png?e=.png -> fzu_images\img_019.png
==================================================
下载完成!成功保存 19/19 张图片到文件夹:...\fzu_images

文件夹内容(共 19 个图片文件):

文件名 大小 文件名 大小
img_001.png 0.5 KB img_011.png 6.2 MB
img_002.png 26.6 KB img_012.jpg 1.3 MB
img_003.png 24.8 KB img_013.png 8.7 MB
img_008.jpg 993 KB img_014.png 837 KB
img_009.png 1.2 MB img_015.png 2.5 MB
img_010.jpg 176 KB img_016.jpg 234 KB

work3_result
先进网页
image

下载结果

work3_folder

2)、心得体会

这个任务让我掌握了批量媒体文件下载的标准流程:解析页面 → 提取资源地址 → 补全相对路径 → 二进制下载落盘。其中最关键的一点是处理相对路径:网页里图片的 src 经常是 images/logo.png 或 /__local/... 这样的相对地址,直接拿来 requests.get() 会报错,必须用 urllib.parse.urljoin(base, src) 拼接成完整 URL。另外一个细节是有些图片地址带 ?e=.png 这样的查询参数,在 os.path.splitext 后用 split("?")[0] 去掉,才能生成合法的本地文件名。下载图片时一定要以二进制模式 "wb" 打开文件并写入 resp.content,如果误用文本模式会导致图片损坏。通过这次练习,我也理解了爬虫对站点的礼貌使用——控制请求频率、不加大并发,是一个爬虫开发者应有的自觉。


总结

通过第一次数据采集作业,我系统练习了三种最常用的网页数据获取方式:

  1. HTML 表格解析:针对服务端渲染的网页,用 BeautifulSoup 定位 <tbody> 下的 <tr> 行和 <td> 单元格,精准提取结构化数据;
  2. 正则提取:针对结构相对固定的列表页,用 re 精准配对字段;
  3. 媒体批量下载:针对资源类页面,用 BeautifulSoup 批量定位 <img> 并二进制落盘。

同时也踩过并解决了中文乱码、反爬拦截、相对路径、字段错位、图片损坏等典型问题,为后续更复杂的数据采集任务打下了基础。

posted on 2026-10-11 10:23  寒渊666  阅读(10)  评论(0)    收藏  举报