数据采集第一次作业
Python 定向爬虫实践作业
实验日期:2026年10月13日
本次实践包括大学排名爬取、商城商品比价和网页图片下载三个任务。每项作业按照“实验”和“心得体会”展开,给出实现思路、完整代码和实验结果。
作业①
1)大学排名爬取实验
实验要求
使用 requests 和 BeautifulSoup 定向爬取 2020年软科中国大学排名,在屏幕上打印排名、学校名称、省市、学校类型和总分。
实现思路
首先通过 requests.get() 获取页面,并按 UTF-8 解码,防止中文乱码。然后使用 BeautifulSoup 解析 HTML,定位表格中的 tbody tr,依次读取每行的 td 单元格。
学校名称单元格还包含英文名称和学校标签,因此通过 .name-cn 只提取中文校名。其余字段按照表格列的位置读取,最后使用制表符打印结果。本代码读取服务器返回的当前 HTML 页面中的学校记录,不操作网页上的动态翻页按钮。
完整代码
import requests
from bs4 import BeautifulSoup
URL = "https://www.shanghairanking.cn/rankings/bcur/2020"
HEADERS = {"User-Agent": "Mozilla/5.0"}
def parse_ranking(html):
soup = BeautifulSoup(html, "html.parser")
records = []
for row in soup.select("tbody tr"):
cells = row.find_all("td", recursive=False)
if len(cells) < 5:
continue
name = cells[1].select_one(".name-cn")
if name is None:
continue
records.append([
cells[0].get_text(strip=True), name.get_text(strip=True),
cells[2].get_text(strip=True), cells[3].get_text(strip=True),
cells[4].get_text(strip=True)
])
if not records:
raise ValueError("未找到排名表格,请检查网页结构或返回内容")
return records
def main():
response = requests.get(URL, headers=HEADERS, timeout=(10, 30))
response.raise_for_status()
records = parse_ranking(response.content.decode("utf-8"))
print("排名\t学校名称\t省市\t学校类型\t总分")
for record in records:
print("\t".join(record))
print(f"\n共读取当前 HTML 页面中的 {len(records)} 所学校。")
if __name__ == "__main__":
try:
main()
except (requests.RequestException, ValueError) as error:
print("爬取失败:", error)
运行命令
python task1_ranking.py
实验结果

2)心得体会
通过本次实验,我熟悉了从网页请求到表格解析再到屏幕输出的基本流程。获取到网页内容后,需要先分析页面结构,再选择合适的标签和 CSS 选择器。直接读取学校名称所在单元格的全部文本会混入英文校名等信息,定位 .name-cn 可以使输出更加准确。
我也认识到,编码处理与异常检查会影响爬虫的可用性。中文页面应采用正确的编码进行解码;当请求失败或表格不存在时,应提示问题,而不是只输出一个空表头。此外,页面可见的分页并不意味着 requests 会自动取得全部分页数据,需要根据实际需求单独处理。
作业②
1)商城商品比价爬取实验
实验要求
自选一个商城,使用 requests 和 re 编写商品比价定向爬虫,以“书包”为关键词获取搜索页面数据,打印序号、价格和商品名称。
实现思路
本实验选择当当网,搜索页面为 当当网商品搜索。将“书包”按照 GBK 编码后作为查询参数发送,并使用 GB18030 解码返回的页面。
使用正则表达式先匹配每个商品对应的 li 块,再在同一个商品块中提取商品名称和价格。价格标签兼容 price_n 与 search_now_price 两种类名。这样可以避免分别提取名称列表和价格列表后发生错位。
提取价格中的数字并转换为 Decimal,按价格升序排列后输出。该排序比较的是不同商品的页面展示价格,商品规格和款式可能不同,不能据此认定为同一型号的最低成交价。
完整代码
import re
import requests
from html import unescape
from decimal import Decimal
URL = "https://search.dangdang.com/"
HEADERS = {"User-Agent": "Mozilla/5.0"}
def parse_products(html):
# 在每个商品 li 内配对名称和价格,防止两份列表错位。
blocks = re.findall(
r'<li\b[^>]*\bid=["\'](\d+)["\'][^>]*>(.*?)</li>',
html, re.S | re.I
)
products = {}
for product_id, block in blocks:
name = re.search(r'<a\b[^>]*\btitle=["\'](.*?)["\']',
block, re.S | re.I)
price = re.search(
r'<span\b[^>]*class=["\'][^"\']*\b'
r'(?:price_n|search_now_price)\b[^"\']*["\'][^>]*>'
r'(.*?)</span>', block, re.S | re.I
)
if not (name and price):
continue
price_text = unescape(re.sub(r'<[^>]+>', '', price.group(1)))
number = re.search(r'\d[\d,]*(?:\.\d+)?', price_text)
if number:
title = re.sub(r'\s+', ' ', unescape(name.group(1))).strip()
value = Decimal(number.group().replace(',', ''))
products[product_id] = (value, title)
if not products:
raise ValueError("未找到商品名称和价格,请检查网页结构或返回内容")
return sorted(products.values(), key=lambda item: item[0])
def main():
# 当当的搜索关键词使用 GBK 编码;requests 对 bytes 参数进行 URL 编码。
response = requests.get(
URL, params={"key": "书包".encode("gbk"), "act": "input",
"page_index": 1},
headers=HEADERS, timeout=(10, 30)
)
response.raise_for_status()
products = parse_products(response.content.decode("gb18030"))
print("序号\t价格\t商品名")
for index, (price, title) in enumerate(products, 1):
print(f"{index}\t{price:.2f}\t{title}")
print(f"\n共读取 {len(products)} 件商品,按页面价格升序排列。")
if __name__ == "__main__":
try:
main()
except (requests.RequestException, ValueError) as error:
print("爬取失败:", error)
运行命令
python task2_shopping.py
实验结果

2)心得体会
通过本次实验,我学习了利用正则表达式提取指定网页字段的方法。提取商品信息时,名称与价格必须属于同一个商品块,不能仅依赖两份独立列表的位置对应。商品块中的内容可能跨越多行,因此匹配时需要使用 re.S;对于名称中的 HTML 实体,则需要使用 unescape() 还原。
商品价格应转换为数值后再排序,直接按照字符串排序可能产生错误。使用 Decimal 也便于保留金额的精度。与标签解析相比,正则表达式更依赖页面结构,网页类名或标签变化后应重新检查匹配规则。本次实现同时兼容两种价格类名,减少了旧规则无法匹配当前页面的问题。
作业③
1)网页图片下载实验
实验要求
爬取 福州大学新闻网影像福大页面 中 JPEG、JPG 或 PNG 格式的图片,将图片文件保存在一个文件夹中。本报告选取8张下载图片作为实验结果展示。
实现思路
首先获取页面 HTML,通过 BeautifulSoup 提取 src、data-src、data-original、href 和 srcset 等属性中的图片候选地址,同时检查页面内的 CSS 背景图片地址。
使用 urljoin() 将相对地址转换为完整地址,通过 urlsplit() 获取 URL 路径,判断后缀是否为 JPG、JPEG 或 PNG。使用集合去除重复地址后,通过 requests 请求图片的二进制数据。
保存前检查 PNG 或 JPEG 的文件签名,避免将错误页面保存为图片。文件名包含序号和图片 URL 的哈希片段,减少名称冲突。图片保存到脚本所在目录的 fzu_images 文件夹中。
代码下载当前页面 HTML 直接引用的全部符合条件的图片,不自动进入新闻详情页;以下结果仅选取其中8张展示。
完整代码
import re
import time
import hashlib
from pathlib import Path
from urllib.parse import urljoin, urlsplit, urldefrag
import requests
from bs4 import BeautifulSoup
URL = "https://news.fzu.edu.cn/yxfd.htm"
FOLDER = Path(__file__).resolve().parent / "fzu_images"
HEADERS = {"User-Agent": "Mozilla/5.0"}
def find_images(html, page_url):
soup = BeautifulSoup(html, "html.parser")
base = soup.find("base", href=True)
base_url = urljoin(page_url, base["href"]) if base else page_url
candidates = []
for tag in soup.find_all(True):
for attribute in ("src", "data-src", "data-original", "href"):
value = tag.get(attribute)
if value:
candidates.append(value)
for attribute in ("srcset", "data-srcset"):
for item in tag.get(attribute, "").split(","):
if item.strip():
candidates.append(item.strip().split()[0])
candidates.extend(re.findall(
r'url\(\s*["\']?([^\)"\']+)', tag.get("style", ""), re.I))
for style in soup.find_all("style"):
candidates.extend(re.findall(
r'url\(\s*["\']?([^\)"\']+)', style.get_text(), re.I))
urls = set()
for candidate in candidates:
url = urldefrag(urljoin(base_url, candidate.strip()))[0]
parts = urlsplit(url)
if parts.scheme in ("http", "https") and re.search(
r'\.(?:jpe?g|png)$', parts.path, re.I):
urls.add(url)
return sorted(urls)
def main():
with requests.Session() as session:
session.headers.update(HEADERS)
response = session.get(URL, timeout=(10, 30))
response.raise_for_status()
urls = find_images(response.content.decode("utf-8"), response.url)
if not urls:
raise ValueError("当前 HTML 中没有找到 JPG、JPEG 或 PNG 图片")
FOLDER.mkdir(parents=True, exist_ok=True)
success = 0
for index, url in enumerate(urls, 1):
try:
image = session.get(url, headers={"Referer": response.url},
timeout=(10, 30))
image.raise_for_status()
data = image.content
# 检查文件签名,避免把错误页面当成图片保存。
if data.startswith(b"\x89PNG\r\n\x1a\n"):
extension = ".png"
elif data.startswith(b"\xff\xd8\xff"):
extension = ".jpg"
else:
raise ValueError("返回内容不是 PNG 或 JPEG 文件")
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
target = FOLDER / f"{index:03d}_{digest}{extension}"
target.write_bytes(data)
success += 1
print("已保存:", target.name)
except (requests.RequestException, ValueError, OSError) as error:
print("下载失败:", url, error)
time.sleep(0.3)
print(f"\n找到 {len(urls)} 个图片地址,成功保存 {success} 张。")
print("保存目录:", FOLDER)
if __name__ == "__main__":
try:
main()
except (requests.RequestException, ValueError, OSError) as error:
print("爬取失败:", error)
运行命令
python task3_images.py
实验结果
程序已成功下载页面图片。以下8张图片均选自实际保存的文件,作为本次实验的展示结果;不重复展示页面中的图标和装饰图片。
图片1

图片2

图片3

图片4

图片5

图片6

图片7

2)心得体会
通过本次实验,我掌握了网页图片地址提取与二进制文件下载的方法。网页中的图片地址可能是相对路径,也可能包含查询参数,所以不能仅检查整个 URL 的结尾;先补全地址,再检查 URL 路径中的扩展名,判断会更准确。页面中同一张图片可能被多次引用,使用集合去重可以减少重复请求。
保存图片时还需要区分真正的图片数据和服务器返回的错误内容。检查文件签名可以避免生成无法打开的“图片文件”。通过给文件名增加序号和 URL 哈希,我也减少了不同地址使用相同文件名时的覆盖问题。本报告从下载结果中选取8张图片,展示了图片采集与本地保存的效果。

浙公网安备 33010602011771号