数据采集第一次作业
数据采集第1次作业
作业①
1、实验:爬取 2020 软科中国大学排名
题目要求:用 requests 和 BeautifulSoup 库方法定向爬取给定网址的数据,屏幕打印爬取的大学排名信息(排名、学校名称、省市、学校类型、总分)。
目标网址:https://www.shanghairanking.cn/rankings/bcur/2020
分析网页结构:
【网页结构分析截图:用 F12 查看 tbody 表格结构,展示排名 div、学校名称 span、td 列的位置】

思路:用 requests.get() 请求页面 → 用 response.apparent_encoding 自适应解码避免中文乱码 → 用 BeautifulSoup 解析 HTML → 通过 soup.select("tbody tr") 选中表格每一行 → 排名取带 ranking 关键字的 div,学校名称取 span.name-cn(避免混入英文名和"双一流/985"标签),省市、类型、总分依次取第 3~5 个 td → 打印并保存 CSV。
完整代码(task1_university_rank.py):
# -*- coding: utf-8 -*-
"""
作业①:使用 requests + BeautifulSoup 定向爬取「软科中国大学排名(2020)」
目标网址:https://www.shanghairanking.cn/rankings/bcur/2020
爬取字段:排名、学校名称、省市、学校类型、总分
输出方式:在屏幕上按表格形式打印,并保存为 CSV 文件(university_rank_2020.csv)
"""
import csv
import unicodedata
import requests
from bs4 import BeautifulSoup
# 目标页面地址
URL = "https://www.shanghairanking.cn/rankings/bcur/2020"
# 请求头:伪装成浏览器,避免被服务器拒绝
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)
}
# CSV 输出路径
CSV_PATH = "university_rank_2020.csv"
def fetch_html(url):
"""请求目标网址并返回其 HTML 源码文本。"""
response = requests.get(url, headers=HEADERS, timeout=15)
response.raise_for_status() # 状态码非 200 时直接抛出异常
# 让 requests 依据网页内容自动判断编码,避免中文乱码
response.encoding = response.apparent_encoding or response.encoding
return response.text
def parse_rankings(html):
"""从页面 HTML 中解析大学排名信息,返回 (排名, 学校名称, 省市, 学校类型, 总分) 列表。"""
soup = BeautifulSoup(html, "html.parser")
rows = []
# 排名表直接渲染在 <tbody> 的 <tr> 行里,选中每一行
for tr in soup.select("tbody tr"):
tds = tr.find_all("td")
if len(tds) < 5:
continue # 跳过结构异常的表格行
# 排名:位于带 ranking 关键字的 div 中
rank_tag = tr.select_one("div[class*='ranking']")
rank = rank_tag.text.strip() if rank_tag else tds[0].get_text(strip=True)
# 学校名称:优先取“中文校名”所在的 span.name-cn,避免混入英文名和标签
name_tag = tds[1].select_one("span.name-cn")
name = name_tag.text.strip() if name_tag else tds[1].get_text(strip=True)
province = tds[2].get_text(strip=True) # 省市
school_type = tds[3].get_text(strip=True) # 学校类型
total_score = tds[4].get_text(strip=True) # 总分
rows.append((rank, name, province, school_type, total_score))
return rows
def display_width(text):
"""计算字符串在终端中的显示宽度(中文等全角字符按 2 列计算)。"""
return sum(2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 for ch in text)
def pad(text, width):
"""按显示宽度在右侧补空格,使各列能够对齐。"""
return text + " " * max(0, width - display_width(text))
def print_table(rows):
"""按“排名 / 学校名称 / 省市 / 学校类型 / 总分”的格式打印排名表格。"""
widths = (6, 22, 10, 10, 8)
headers = ("排名", "学校名称", "省市", "学校类型", "总分")
header_line = "".join(pad(h, w) for h, w in zip(headers, widths))
print(header_line)
print("-" * display_width(header_line))
for row in rows:
print("".join(pad(cell, w) for cell, w in zip(row, widths)))
def save_csv(rows):
"""将爬取结果保存为 CSV 文件。"""
with open(CSV_PATH, "w", newline="", encoding="utf-8-sig") as fp:
writer = csv.writer(fp)
writer.writerow(["排名", "学校名称", "省市", "学校类型", "总分"])
writer.writerows(rows)
print(f"\n结果已保存到:{CSV_PATH}")
def main():
"""程序入口:抓取页面 -> 解析数据 -> 打印并保存。"""
html = fetch_html(URL)
rankings = parse_rankings(html)
print(f"共爬取到 {len(rankings)} 条排名数据:\n")
print_table(rankings)
save_csv(rankings)
if __name__ == "__main__":
main()
运行结果:

运行输出:
共爬取到 30 条排名数据:
排名 学校名称 省市 学校类型 总分
--------------------------------------------------------
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
21 天津大学 天津 理工 390.3
22 山东大学 山东 综合 387.9
23 厦门大学 福建 综合 383.3
24 吉林大学 吉林 综合 379.5
25 华南理工大学 广东 理工 379.4
26 中南大学 湖南 综合 378.6
27 大连理工大学 辽宁 理工 365.1
28 西北工业大学 陕西 理工 359.6
29 华东师范大学 上海 师范 358.0
30 中国农业大学 北京 农业 351.5
2、心得体会
这道题是标准的静态表格爬取流程:发请求 → 解码 HTML → 用 BeautifulSoup 定位表格 → 提取字段。
几个关键点:
- 编码处理:中文网页直接
decode("utf-8")容易踩坑,用response.apparent_encoding让 requests 自动识别编码更稳妥。 - 校名提取:学校名称那一格里中文名、英文名、"双一流/985"标签是叠在一起的,直接取整格文本会带出英文,必须落到
span.name-cn上才只剩中文。 - 打印对齐:终端里中文占两格,按字符数补空格会让每一列逐行错开,用
unicodedata.east_asian_width算显示宽度再补空格才对得上。 - 数据落地:除了屏幕打印,顺手存了一份 CSV(
utf-8-sig编码,Excel 打开不乱码)。
通过这道题,我理解了 BeautifulSoup 的一般过程:先选大容器(tbody tr),再选子节点,最后取文本。
作业②
1、实验:当当网"书包"商品比价爬虫
题目要求:用 requests 和 re 库方法设计某个商城(自己选择)商品比价定向爬虫,爬取该商城以关键词"书包"搜索页面的数据,爬取商品名称和价格,按"序号、价格、商品名"输出。
目标网站:当当网搜索页( https://search.dangdang.com/?key=����&act=input&page_index=1 )
分析网页:
【网页结构分析截图:用 F12 观察商品块 li 的 ddt-pit 属性、商品名 a 的 title、价格 span 的 class="price_n"】

思路:先抓包看搜索页结构,发现每个商品块都在 <li ddt-pit="...">...</li> 里;商品名在 <p class="name" name="title"><a title="...">,价格在 <span class="price_n">¥xx</span>。用正则先按商品块切分,块内再分别匹配商品名和价格,避免两次全局匹配导致字段错位。注意当当搜索页是 gb2312/gbk 编码,必须显式设 response.encoding = "gbk",否则整页乱码。
完整代码(task2_dangdang_price.py):
# -*- coding: utf-8 -*-
"""
作业②:使用 requests + re 库实现商城商品比价定向爬虫
商城选择:当当网( https://www.dangdang.com )
搜索关键词:书包
爬取字段:序号、价格、商品名
输出方式:在屏幕上按表格形式打印,并保存为 CSV 文件(dangdang_price.csv)
"""
import csv
import re
import requests
# 当当网搜索接口
SEARCH_URL = "https://search.dangdang.com/"
KEYWORD = "书包"
# 请求头:伪装成浏览器,避免被服务器拒绝
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)
}
# CSV 输出路径
CSV_PATH = "dangdang_price.csv"
def fetch_search_page(keyword, page=1):
"""请求商品搜索页并返回 HTML 文本。"""
params = {"key": keyword, "act": "input", "page_index": page}
response = requests.get(SEARCH_URL, params=params, headers=HEADERS, timeout=15)
response.raise_for_status()
# 当当搜索页使用 gb2312/gbk 编码,必须显式指定,否则中文乱码
response.encoding = "gbk"
return response.text
def parse_products(html):
"""用正则从搜索页 HTML 中提取商品名称与价格,返回 (商品名, 价格) 列表。"""
products = []
# 1) 按商品块切分:每个商品位于 <li ddt-pit="..."> ... </li>
item_blocks = re.findall(r'<li[^>]*ddt-pit="\d+"[^>]*>(.*?)</li>', html, re.S)
for block in item_blocks:
# 2) 商品名称:位于 <p class="name" name="title"><a title="商品名"...>
name_match = re.search(
r'<p class="name"[^>]*name="title"[^>]*>.*?<a[^>]*title="([^"]+)"',
block, re.S,
)
# 3) 商品价格:位于 <span class="price_n">¥146.00</span>
price_match = re.search(r'<span class="price_n">[^0-9]*([0-9.]+)</span>', block)
if name_match and price_match:
name = name_match.group(1).strip() # 去掉标题两侧空白
price = price_match.group(1).strip() # 价格数值
products.append((name, price))
return products
def print_table(products):
"""按“序号 / 价格 / 商品名”的格式打印比价结果。"""
print(f"{'序号':<6}{'价格':<12}{'商品名'}")
print("-" * 80)
for index, (name, price) in enumerate(products, start=1):
print(f"{index:<6}{price:<12}{name}")
def save_csv(products):
"""将比价结果保存为 CSV 文件。"""
with open(CSV_PATH, "w", newline="", encoding="utf-8-sig") as fp:
writer = csv.writer(fp)
writer.writerow(["序号", "价格", "商品名"])
for index, (name, price) in enumerate(products, start=1):
writer.writerow([index, price, name])
print(f"\n结果已保存到:{CSV_PATH}")
def main():
"""程序入口:搜索商品 -> 正则解析 -> 打印并保存。"""
html = fetch_search_page(KEYWORD)
products = parse_products(html)
print(f"当当网关键词「{KEYWORD}」共爬取到 {len(products)} 件商品:\n")
print_table(products)
save_csv(products)
if __name__ == "__main__":
main()
运行结果:


2、心得体会
这道题用的流程是:发请求 → 解码 HTML → 用正则提取字段。
几个关键点:
- 编码是最大的坑:当当搜索页是 gb2312 编码,一开始没设
response.encoding时整页乱码,连"书包"都匹配不到;显式设成"gbk"后立刻正常。 - 按块切分比全局匹配稳:如果直接在整页 HTML 上两次
findall分别抓名字和价格,一旦某个商品缺字段就会错位。先按ddt-pit商品块切分,再在块内匹配,每条记录天然一一对应。 - 商品名定位:一个商品块里
title属性出现多次(图片链接、店铺名都带),直接取第一个容易取错,靠<p class="name" name="title">下的a[title]才稳。 - 正则的取舍:写起来轻量,不依赖解析库;但页面一旦改版,正则就要跟着改,抗造程度不如 BeautifulSoup 的 DOM 解析。
通过这道题,我加深了对"抓包 + 分析 HTML 结构 + 写正则"流程的理解。
作业③
1、实验:爬取给定网页的所有 JPEG、JPG、PNG 图片
题目要求:爬取一个给定网页( https://news.fzu.edu.cn/yxfd.htm ) 或自选网页的所有 JPEG、JPG、PNG 格式图片文件。
目标网页:https://news.fzu.edu.cn/yxfd.htm (福州大学新闻网-院系动态)
分析网页:
【网页结构分析截图:用 F12 查看 img 标签列表,注意 src 有相对路径也有 / 开头的站内绝对路径,部分 src 带 ?e=.png 查询串】

思路:
- 用
requests.get()拿到页面 HTML; - 用正则
re.findall(r'<img[^>]+src=["\']([^"\']+)["\']', html, re.I)遍历所有<img>的 src; - 先
split("?")[0]去掉?e=.png这类查询串,再判断后缀是否为.jpg / .jpeg / .png; - 用
urljoin()把相对路径拼成绝对 URL,用set()去重; - 逐张
requests.get()拿到二进制内容,用"wb"模式写进本地images/文件夹;重名文件自动加序号,单张失败 try/except 跳过。
完整代码(task3_download_images.py):
# -*- coding: utf-8 -*-
"""
作业③:爬取给定网页中所有 JPEG / JPG / PNG 格式的图片并保存到本地文件夹
目标网页:https://news.fzu.edu.cn/yxfd.htm (福州大学新闻网-院系动态)
保存目录:images/
输出方式:逐个下载图片并在屏幕上打印下载结果
"""
import os
import re
from urllib.parse import urljoin, urlparse
import requests
# 目标网页地址
PAGE_URL = "https://news.fzu.edu.cn/yxfd.htm"
# 图片保存目录
SAVE_DIR = "images"
# 允许下载的图片后缀
IMAGE_EXTENSIONS = (".jpg", ".jpeg", ".png")
# 请求头:伪装成浏览器,避免被服务器拒绝
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)
}
def fetch_html(url):
"""请求目标网页并返回 HTML 文本。"""
response = requests.get(url, headers=HEADERS, timeout=15)
response.raise_for_status()
response.encoding = response.apparent_encoding or "utf-8"
return response.text
def find_image_urls(html, base_url):
"""从 HTML 中找出所有 jpg/jpeg/png 图片的绝对 URL(去重且保持出现顺序)。"""
urls = []
seen = set()
# 匹配 <img ... src="...">(同时兼容单引号和双引号)
for src in re.findall(r'<img[^>]+src=["\']([^"\']+)["\']', html, re.I):
# 去掉 ?e=.png 之类的查询串后判断文件后缀
path = src.split("?")[0].lower()
if not path.endswith(IMAGE_EXTENSIONS):
continue
abs_url = urljoin(base_url, src) # 相对路径补全为绝对路径
if abs_url in seen:
continue
seen.add(abs_url)
urls.append(abs_url)
return urls
def download_images(urls, save_dir):
"""将所有图片下载并保存到指定目录,单张失败不影响整体。"""
os.makedirs(save_dir, exist_ok=True)
used_names = set() # 记录已使用的文件名,避免重名覆盖
success = 0
for index, url in enumerate(urls, start=1):
try:
response = requests.get(url, headers=HEADERS, timeout=15)
response.raise_for_status()
# 由 URL 推断文件名
filename = os.path.basename(urlparse(url).path)
if not filename:
filename = f"image_{index}.jpg"
# 若文件名重复,则追加序号
if filename in used_names:
stem, ext = os.path.splitext(filename)
filename = f"{stem}_{index}{ext}"
used_names.add(filename)
# 以二进制方式写入图片内容
with open(os.path.join(save_dir, filename), "wb") as fp:
fp.write(response.content)
success += 1
print(f"[{index:>2}] 下载成功:{filename} <- {url}")
except Exception as exc: # noqa: BLE001
# 单个失败不影响整体
print(f"[{index:>2}] 下载失败:{url} ({exc})")
return success
def main():
"""程序入口:抓取网页 -> 提取图片链接 -> 下载保存。"""
html = fetch_html(PAGE_URL)
image_urls = find_image_urls(html, PAGE_URL)
print(f"在 {PAGE_URL} 中共发现 {len(image_urls)} 张图片,开始下载……\n")
success = download_images(image_urls, SAVE_DIR)
print(f"\n完成:成功下载 {success}/{len(image_urls)} 张图片,保存目录:{SAVE_DIR}/")
if __name__ == "__main__":
main()
运行结果:
【运行结果截图 1:终端下载日志】

【运行结果截图 2:images 文件夹中的 19 张图片】

2、心得体会
这道题的流程是:发请求拿 HTML → 提取所有 img 的 src → 过滤后缀 → 拼绝对路径 → 逐张下载二进制写文件。
几个关键点:
- 相对路径要补全:页面里的 src 有的是相对路径(如
images/logo.png),有的是/开头的站内绝对路径,不能直接请求,用urljoin(base_url, src)统一拼成完整 URL 后逻辑就一致了。 - 后缀判断前先处理查询串:有的 src 长这样
xxx.png?e=.png,直接endswith判断后缀会漏掉,必须先split("?")[0]拿到干净路径再判断。 - 只取题目要求的格式:页面里还有 .gif 和图标,按
.jpg/.jpeg/.png后缀过滤掉即可。 - 健壮性处理:
os.makedirs(..., exist_ok=True)建目录、二进制写入、重名文件自动加序号避免互相覆盖、单张失败 try/except 跳过不中断整个循环。
通过这道题,我进一步加深了对爬取图片这类二进制数据的理解:文本用字符串处理,图片等二进制资源则要拿到 response.content 后用 "wb" 模式写文件。

浙公网安备 33010602011771号