数据采集第一次
一.作业一
1.1
要求:
用requests和BeautifulSoup库方法定向爬取给定网址(http://www.shanghairanking.cn/rankings/bcur/2020 )的数据,屏幕打印爬取的大学排名信息。
分析:
(1)定位数据文件:
在开发者模式里,Fetch/XHR只有collect,随后发现静态资源地址:/_nuxt/static/1791618681/rankings/bcur/202011/payload.js,并且包univNameCn、province、univCategory、score、ranking 等字段,于是下载payload.js。最初直接请求静态资源时,服务器返回 403 ,因此使用 Requests 的 Session 建立会话。
(2)payload.js结构:
数据被作为第二个参数传给 NUXT_JSONP 函数,普通 json.loads() 无法直接解析,因此先用 Node.js 执行 JS,再回传 JSON 给 Python。然后从 univData 中每条记录提取univNameCn、province等信息。

代码:
# payload.js获取
import requests
page_url = "https://www.shanghairanking.cn/rankings/bcur/2020"
payload_url = (
"https://www.shanghairanking.cn/_nuxt/static/"
"1791618681/rankings/bcur/202011/payload.js"
)
session = requests.Session()
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36"
),
"Accept": "*/*",
"Referer": page_url,
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
}
# 第一步:先访问原网页
page_response = session.get(
page_url,
headers=headers,
timeout=20
)
print("网页状态码:", page_response.status_code)
# 第二步:在同一个会话中请求静态资源
payload_response = session.get(
payload_url,
headers=headers,
timeout=20
)
print("静态资源状态码:", payload_response.status_code)
print("响应内容:", payload_response.text[:300])
if payload_response.status_code == 200:
with open("payload.js", "w", encoding="utf-8") as f:
f.write(payload_response.text)
print("下载成功,已保存 payload.js")
else:
print("请求仍然失败,请使用浏览器检查该资源的真实请求信息。")
import requests
import subprocess
import json
import os
PAGE_URL = "https://www.shanghairanking.cn/rankings/bcur/2020"
PAYLOAD_URL = (
"https://www.shanghairanking.cn/_nuxt/static/"
"1791618681/rankings/bcur/202011/payload.js"
)
HEADERS = {
"User-Agent": "Mozilla/5.0",
"Referer": PAGE_URL
}
# 1. 下载静态数据
session = requests.Session()
# 先访问网页,建立会话
session.get(PAGE_URL, headers=HEADERS, timeout=20).raise_for_status()
response = session.get(
PAYLOAD_URL,
headers=HEADERS,
timeout=20
)
response.raise_for_status()
with open("payload.js", "w", encoding="utf-8") as f:
f.write(response.text)
print("静态数据下载成功!")
# 2. 创建 JavaScript 解析脚本
# Nuxt JSONP 文件会把数据作为第二个参数传入
js_code = r"""
const fs = require('fs');
let result = null;
global.__NUXT_JSONP__ = function(path, data) {
result = data;
};
const source = fs.readFileSync('payload.js', 'utf8');
eval(source);
if (!result) {
throw new Error('没有成功解析 Nuxt 页面数据');
}
console.log(JSON.stringify(result));
"""
with open("parse_payload.js", "w", encoding="utf-8") as f:
f.write(js_code)
# 3. 执行 JavaScript,获取解析后的数据
process = subprocess.run(
["node", "parse_payload.js"],
capture_output=True,
text=True,
encoding="utf-8"
)
if process.returncode != 0:
print("解析失败:")
print(process.stderr)
raise SystemExit(1)
try:
page_data = json.loads(process.stdout)
except json.JSONDecodeError:
print("数据转换 JSON 失败,请检查 JavaScript 输出。")
raise SystemExit(1)
# 4. 查找大学数据
def find_univ_data(obj):
if isinstance(obj, dict):
if isinstance(obj.get("univData"), list):
return obj["univData"]
for value in obj.values():
found = find_univ_data(value)
if found is not None:
return found
elif isinstance(obj, list):
for value in obj:
found = find_univ_data(value)
if found is not None:
return found
return None
universities = find_univ_data(page_data)
if universities is None:
print("当前页面数据中没有找到 univData 数组。")
raise SystemExit(0)
# 5. 输出排名信息
print(f"\n共解析到 {len(universities)} 条大学记录\n")
print("排名\t学校名称\t省市\t学校类型\t总分")
for university in universities:
rank = university.get("ranking", "")
name = university.get("univNameCn", "")
province = university.get("province", "")
school_type = university.get("univCategory", "")
score = university.get("score", "")
print(f"{rank}\t{name}\t{province}\t{school_type}\t{score}")
结果:

1.2心得体会
最开始我期望找到类似 /api/xxx 的接口请求。然而逐一查看所有 XHR 请求也没发现与院校排名相关的接口。然后在AI的提示下查看payload.js,在Response 中看到了完整的排名数据。然后输入某大学的名称,命中payload,证明了这一点。
二.作业2
2.1
要求:
用requests和re库方法设计某个商城(自已选择)商品比价定向爬虫,爬取该商城,以关键词“书包”搜索页面的数据,爬取商品名称和价格。
分析:
(1)URL:
搜索“书包”后,观察地址栏发现基础URL为 http://search.dangdang.com/,搜索参数为 key=书包,并带有 act=input 和 page_index=1。关键词“书包”在URL中会被编码为 %CA%E9%B0%FC,因此代码中需要使用 quote(keyword.encode('gbk')) 进行编码。
(2)网页结构:
使用元素选择器定位商品信息。发现商品名称位于 <p class="name"> 标签下的 <a> 标签的 title 中;商品价格位于 <span class="price_n"> 中。


(3)翻页:
通过url的page_index参数决定。

代码:
import urllib3
import re
import time
import csv
import os
from urllib.parse import quote
# 基础URL和必须的请求头
BASE_URL = "http://search.dangdang.com/"
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'http://search.dangdang.com/',
}
http = urllib3.PoolManager()
def get_html(url):
"""发送 GET 请求获取网页 HTML 文本"""
try:
response = http.request('GET', url, headers=HEADERS, timeout=10.0)
if response.status == 200:
try:
# 当当网使用GBK编码
html = response.data.decode('gbk')
except (UnicodeDecodeError, LookupError):
html = response.data.decode('utf-8', errors='ignore')
return html
else:
print(f"[错误] 请求失败,状态码: {response.status}")
return None
except Exception as e:
print(f"[异常] 网络请求发生错误: {e}")
return None
def parse_html(html):
"""从 HTML 文本中解析商品名称和价格"""
products = []
# 商品名称在 title 属性中
name_pattern = re.compile(r'<p class="name"[^>]*>\s*<a[^>]*title="(.*?)"', re.S)
names = name_pattern.findall(html)
# 价格在 span class="price_n" 中
price_pattern = re.compile(r'<span class="price_n">(.*?)</span>', re.S)
prices = price_pattern.findall(html)
names = [re.sub(r'\s+', ' ', name).strip() for name in names]
prices = [re.sub(r'[^\d.]', '', price.strip()) for price in prices]
for name, price in zip(names, prices):
products.append({'name': name, 'price': price})
return products
def main():
keyword = "书包"
encoded_keyword = quote(keyword.encode('gbk')) # GBK编码
max_items = 70
total_pages = 2
all_products = []
print(f"开始爬取关键词:【{keyword}】的商品数据...\n")
for page in range(1, total_pages + 1):
print(f"--- 正在抓取第 {page} 页 ---")
url = f"{BASE_URL}?key={encoded_keyword}&act=input&page_index={page}"
html = get_html(url)
if not html:
continue
page_products = parse_html(html)
if not page_products:
break
all_products.extend(page_products)
if len(all_products) >= max_items:
break
time.sleep(1.5)
all_products = all_products[:max_items]
print(f"\n========= 抓取完成,共获取 {len(all_products)} 项商品 =========")
for idx, prod in enumerate(all_products, 1):
print(f"{idx:02d}. {prod['name']} | ¥{prod['price']}")
if __name__ == "__main__":
main()
结果:

2.2 心得体会
1.
当当网页面采用 GBK 编码,需要通过 response.data.decode('gbk') 进行解码,否则会出现中文乱码。在解析数据时,使用正则表达式提取 title 属性和价格标签,能够高效地获取结构化数据。
2.
实现翻页功能时,遇到了代码有翻页功能但实际只抓一页的问题。分析发现是 total_pages = 1 和 max_items = 60 导致的。之前的代码里total_pages = 1 导致 range(1, 2) 只循环一次;同时 max_items = 60,当当网每页正好返回约60个商品,导致第一页抓完就触发了 break 跳出循环。因此修改total_pages = 2,max_items = 70,成功抓取到70条数据。
三.作业3
3.1
要求:
爬取一个给定网页( https://news.fzu.edu.cn/yxfd.htm )或者自选网页的所有JPEG、JPG或PNG格式图片文件
分析:
(1)HTML 结构:
发现配图在 标签中,因此使用 BeautifulSoup 的 find_all('img') 。
(2)图片链接:
查看元素时,发现有这样的标签:<img src="/__local/F/4D/B3/EE9C07C6486DEFAA9EAF683C37C_48D55A5D_F84E4.jpg" alt>。如果直接对 /__local/... 发起 requests.get(),程序会报错。因此不能直接提取 src ,而是要用 urljoin(target_url, src) 拼成绝对路径。

(3)翻页处理:
注意到第一页是 yxfd.htm,第二页是 yxfd/6.htm,直到第七页是 yxfd/1.htm。因此先构建基础路径 base = "https://news.fzu.edu.cn/yxfd",第一页单独写f"{base}.htm",后续页用f"{base}/{n}.htm" for n in range(6, 0, -1)。

代码:
import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
def download_images(target_url, save_dir="downloaded_images", page_index=0):
"""
下载指定页面的所有 JPEG/JPG/PNG 图片。
page_index: 用于给文件名加前缀,避免不同页面的同名图片冲突。
"""
# 创建保存目录
if not os.path.exists(save_dir):
os.makedirs(save_dir)
print(f"已创建文件夹: {save_dir}")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
}
try:
response = requests.get(target_url, headers=headers, timeout=10)
response.raise_for_status()
response.encoding = response.apparent_encoding
except Exception as e:
print(f"获取网页失败 [{target_url}],错误信息: {e}")
return 0 # 返回 0 表示该页下载失败
soup = BeautifulSoup(response.text, 'html.parser')
img_tags = soup.find_all('img')
if not img_tags:
print(f"在 {target_url} 中未找到任何图片。")
return 0
print(f"[页 {page_index + 1}] {target_url} 共找到 {len(img_tags)} 个 <img> 标签,开始过滤并下载...")
success_count = 0
for index, img in enumerate(img_tags):
src = img.get('src') or img.get('data-src')
if not src:
continue
img_url = urljoin(target_url, src)
base_url = img_url.split('?')[0]
# 只下载 JPEG / JPG / PNG
if base_url.lower().endswith(('.jpg', '.jpeg', '.png')):
try:
img_response = requests.get(img_url, headers=headers, timeout=10)
if img_response.status_code == 200:
filename = os.path.basename(base_url)
if not filename:
filename = f"image_{index}.jpg"
# 文件名加上页面序号前缀,避免多页之间的重名覆盖
filename = f"p{page_index}_{index}_{filename}"
file_path = os.path.join(save_dir, filename)
with open(file_path, 'wb') as f:
f.write(img_response.content)
print(f" 成功下载: {filename}")
success_count += 1
except Exception as e:
print(f" 下载图片失败 [{img_url}]: {e}")
return success_count
def main():
# 构建 7 个页面的 URL 列表
base = "https://news.fzu.edu.cn/yxfd"
# 第一页: /yxfd.htm,后续页: /yxfd/6.htm ~ /yxfd/1.htm
page_urls = [f"{base}.htm"] + [f"{base}/{n}.htm" for n in range(6, 0, -1)]
save_dir = "fzu_images"
total_success = 0
for idx, url in enumerate(page_urls):
print(f"\n{'='*50}")
print(f"正在处理第 {idx + 1}/7 页: {url}")
print(f"{'='*50}")
count = download_images(url, save_dir=save_dir, page_index=idx)
total_success += count
print(f"\n{'='*50}")
print(f"全部任务完成!共成功下载 {total_success} 张图片,保存在 '{save_dir}' 文件夹中。")
if __name__ == "__main__":
main()
结果:


3.2心得体会
1.
最初我以为网页源码中的 src 属性均为可直接请求的完整 URL,导致程序运行时因相对路径而请求失败。随后了解到浏览器会自动为相对路径补全当前域名,但 requests 库不具备这一能力。因此要用 urllib.parse.urljoin 完成绝对路径的拼接。
2.
网站的倒序 URL 结构提醒我在编写循环前必须通过浏览器实际翻页来找到规律,不能想当然。

浙公网安备 33010602011771号