数据采集实践第一次作业
作业①
1)、软科中国大学排名定向爬虫实验
页面结构分析
打开软科2020年中国大学排名页面( http://www.shanghairanking.cn/rankings/bcur/2020 ),使用浏览器开发者工具(F12)审查页面结构。可以发现:学校信息包含在 <tbody> 标签中,每一条学校信息包含在 <tr> 标签中;学校名称包含在 class='univname' 的 div 下的 class='name-cn' 的 span 标签中;排名、省市、学校类型和总分信息在 <td> 标签中,其中排名在 td[0],学校名称在 td[1] 的嵌套span中,省市在 td[2],学校类型在 td[3],总分在 td[4]。

完整代码
"""
作业①:使用requests和BeautifulSoup爬取软科2020中国大学排名
"""
import requests
from bs4 import BeautifulSoup
def get_html_text(url):
"""
向目标URL发送HTTP请求,获取网页HTML文本
:param url: 目标网址
:return: 网页HTML文本字符串,请求失败返回空字符串
"""
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'
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 检查请求是否成功
response.encoding = 'utf-8'
return response.text
except requests.RequestException as e:
print(f'请求失败:{e}')
return ''
def parse_university_list(html):
"""
解析HTML文本,提取大学排名数据
:param html: 网页HTML文本
:return: 包含每所大学信息的列表,每个元素为[排名, 学校名称, 省市, 学校类型, 总分]
"""
soup = BeautifulSoup(html, 'lxml')
table = soup.find('tbody') # 定位排名表格主体
if not table:
print('未找到排名表格,请检查网页结构是否变更')
return []
rows = table.find_all('tr') # 获取所有行
univ_list = []
for row in rows:
# 提取学校名称
school_tag = row.select("div[class='univname'] span[class='name-cn']")
if not school_tag:
continue
school = school_tag[0].text.strip()
# 提取其余字段
tds = row.find_all('td')
if len(tds) < 5:
continue
rank = tds[0].text.strip() # 排名
province = tds[2].text.strip() # 省市
univ_type = tds[3].text.strip() # 学校类型
score = tds[4].text.strip() # 总分
univ_list.append([rank, school, province, univ_type, score])
return univ_list
def print_university_list(univ_list):
"""
格式化打印大学排名信息
:param univ_list: 大学排名数据列表
"""
# 打印表头
print(f"{'排名':<6}{'学校名称':<14}{'省市':<8}{'学校类型':<10}{'总分':<8}")
print('-' * 50)
for univ in univ_list:
rank, school, province, univ_type, score = univ
# 使用全角空格补齐学校名称,保证中文对齐
school_padded = school + '\u3000' * max(0, 12 - len(school))
print(f"{rank:<6}{school_padded}{province:<8}{univ_type:<10}{score:<8}")
def main():
"""主函数:执行爬取、解析和打印流程"""
url = 'http://www.shanghairanking.cn/rankings/bcur/2020'
html = get_html_text(url)
if not html:
return
univ_list = parse_university_list(html)
print_university_list(univ_list)
print(f'\n共爬取到 {len(univ_list)} 所大学的数据')
if __name__ == '__main__':
main()
输出结果

2)、心得体会
本次作业让我掌握了使用 requests 库发送HTTP请求和 BeautifulSoup 库解析HTML的核心流程。技术上的关键收获是:find、find_all 和 CSS 选择器的组合使用能够高效定位嵌套标签中的数据;strip() 方法用于去除提取文本中的空白字符,保证数据整洁。实践中遇到的困难主要是中文对齐问题——学校名称长度不一致导致输出错位,最终通过全角空格(\u3000)填充来解决。
关于爬取条数的思考:
本次实验中,程序实际爬取并打印了 30 所大学的数据,通过分析我发现:软科排名页面采用了分页动态加载技术,初始返回的 HTML 源码中只包含第一页的 30 条数据,后续数据需要点击"下一页"时由 JavaScript 异步请求接口才能获取。
本次实验使用 BeautifulSoup 直接解析初始 HTML 的方式,只获取到前 30 所大学。这次经历让我深刻认识到:BeautifulSoup 只能解析静态 HTML 标签,无法处理动态渲染的内容。这为我后续深入学习动态网页爬取提供了明确的方向。此外,通过添加 raise_for_status() 和 try-except 异常处理,程序在请求失败时能够优雅退出而不会崩溃。
作业②
1)、商城商品比价定向爬虫实验
页面结构分析
本次选择当当网作为目标商城,以关键词“书包”搜索。通过 F12 分析页面源码,确认商品名称位于 <p class="name"> 标签下的 <a> 标签的 title 属性中;商品价格位于 <span class="price_n"> 中(注意:源码中货币符号是 HTML 实体 ¥,而不是直接显示的 ¥)。分页通过 URL 参数 page_index 控制。
核心代码
"""
作业②:使用requests和re库爬取当当网商品比价信息
搜索关键词:书包
"""
import re
import time
import random
from urllib.parse import quote
import requests
def get_html_text(url):
"""向目标URL发送GET请求,获取网页HTML源码"""
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',
'Referer': 'https://www.dangdang.com/',
'Accept-Language': 'zh-CN,zh;q=0.9',
}
try:
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
response.encoding = response.apparent_encoding
return response.text
except requests.RequestException as e:
print(f' [请求失败] {e}')
return ''
def parse_page(item_list, html):
"""使用正则表达式从HTML源码中提取商品名称和价格"""
# 商品名称:<p class="name"> ... <a ... title="商品名">
title_list = re.findall(
r'<p class="name".*?<a[^>]*title="([^"]+)"', html, re.S
)
# 商品价格:<span class="price_n">¥146.00</span>
# 注意:源码中货币符号是 HTML 实体 ¥,不是 ¥ 或 ¥
price_list = re.findall(
r'<span class="price_n">\s*¥\s*([\d.]+)', html
)
print(f' [调试] 本页匹配到标题 {len(title_list)} 个,价格 {len(price_list)} 个')
# 将名称与价格配对存入列表
for i in range(min(len(title_list), len(price_list))):
item_list.append([price_list[i], title_list[i].strip()])
def print_item_list(item_list):
"""格式化打印商品比价信息"""
print(f"\n{'序号':<6}{'价格':<12}商品名称")
print('-' * 70)
for idx, item in enumerate(item_list, start=1):
price, title = item
# 学校名称太长时截断显示
if len(title) > 40:
title = title[:40] + '...'
print(f"{idx:<6}{price:<12}{title}")
def main():
"""主函数:分页爬取当当网指定关键词的商品数据"""
print('==== 程序开始运行 ====')
keyword = '书包'
base_url = 'https://search.dangdang.com/?key=' + quote(keyword) + '&act=input'
depth = 3
item_list = []
for page in range(1, depth + 1):
if page == 1:
url = base_url
else:
url = base_url + '&page_index=' + str(page)
print(f'正在爬取第 {page} 页...')
html = get_html_text(url)
if html:
print(f' [调试] 网页长度:{len(html)}')
parse_page(item_list, html)
else:
print(' [错误] 未获取到网页内容')
# 休眠
if page < depth: # 最后一页后不再休眠
sleep_time = random.uniform(3, 5)
print(f' [休眠] 等待 {sleep_time:.2f} 秒...\n')
time.sleep(sleep_time)
print_item_list(item_list)
print(f'\n共爬取到 {len(item_list)} 件商品')
if __name__ == '__main__':
main()
输出结果
运行代码后,成功爬取了前3页共180件商品的信息,包括价格和商品名称。


2)、心得体会
本次作业我最初尝试了淘宝和京东,但都遇到了反爬问题——淘宝的数据隐藏在加密的接口中,而京东的搜索页已改版为 React 单页应用(SPA),初始 HTML 中根本无法提取数据。最终,我选择了当当网作为目标站点,其搜索页直接返回了包含商品数据的静态 HTML。
在开发过程中,我遇到了一个非常隐蔽的问题:正则表达式一直匹配不到价格。通过打印真实 HTML 片段,我发现了关键原因:源码里的货币符号是 HTML 实体 ¥,而浏览器中看到的是 ¥。这让我深刻理解了“requests 获取的是原始 HTML 源码,不会执行 JavaScript,也不会自动转义 HTML 实体”这一核心知识点。
同时,我也严格限制请求频率,不尝试破解加密接口,将数据仅用于课程学习,体现了数据采集工程师应有的法律意识。
作业③
1)、网页图片文件爬取实验
页面结构分析
以福州大学新闻网“院系风采”页面( https://news.fzu.edu.cn/yxfd.htm )为目标。通过 F12 查看页面源码,图片以 <img src="..."> 标签嵌入在 HTML 中。图片文件格式包括 JPEG、JPG 和 PNG,同时也包含大量 SVG 矢量图标(如返回顶部按钮、搜索按钮)。根据作业要求,正则表达式需要严格过滤,仅保留 jpg|jpeg|png,并排除 SVG。

核心代码
"""
作业③:爬取指定网页中所有JPEG、JPG、PNG格式图片并保存到本地文件夹
目标网页:https://news.fzu.edu.cn/yxfd.htm
"""
import os
import re
import time
import requests
def get_html_text(url):
"""
发送HTTP请求获取网页HTML源码
:param url: 目标网页地址
:return: 网页HTML文本,请求失败返回空字符串
"""
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'
}
try:
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
response.encoding = response.apparent_encoding
return response.text
except requests.RequestException as e:
print(f'获取网页失败:{e}')
return ''
def extract_image_urls(html, base_url):
"""
使用正则表达式从HTML中提取所有图片链接
:param html: 网页HTML文本
:param base_url: 网页基础URL,用于拼接相对路径
:return: 图片完整URL列表
"""
# 匹配 src="..." 中的图片链接,支持 jpg/jpeg/png 格式
pattern = r'src="([^"]+\.(?:jpg|jpeg|png))"'
img_urls = re.findall(pattern, html, re.IGNORECASE)
# 处理相对路径,拼接为完整的URL
full_urls = []
for url in img_urls:
if url.startswith('http://') or url.startswith('https://'):
full_urls.append(url)
elif url.startswith('//'):
full_urls.append('https:' + url)
elif url.startswith('/'):
# 从base_url中提取协议和域名部分
domain = base_url.split('/', 3)[:3]
full_urls.append('/'.join(domain) + url)
else:
# 相对路径,与base_url所在目录拼接
base_dir = base_url.rsplit('/', 1)[0]
full_urls.append(base_dir + '/' + url)
# 去重并保持顺序
return list(dict.fromkeys(full_urls))
def download_images(img_urls, save_dir):
"""
下载图片并保存到指定文件夹
:param img_urls: 图片URL列表
:param save_dir: 保存图片的本地文件夹路径
"""
# 创建保存目录
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/120.0.0.0 Safari/537.36'
}
success_count = 0
for idx, img_url in enumerate(img_urls, start=1):
try:
response = requests.get(img_url, headers=headers, timeout=15)
response.raise_for_status()
# 从URL中提取文件扩展名
ext = img_url.split('.')[-1].lower()
# 生成文件名:序号 + 扩展名
filename = f'{idx:03d}.{ext}'
filepath = os.path.join(save_dir, filename)
# 以二进制方式写入图片文件
with open(filepath, 'wb') as f:
f.write(response.content)
print(f'[{idx}/{len(img_urls)}] 已保存:{filename}')
success_count += 1
time.sleep(0.5) # 避免请求过快
except requests.RequestException as e:
print(f'[{idx}/{len(img_urls)}] 下载失败:{img_url},原因:{e}')
print(f'\n下载完成:成功 {success_count} 张,失败 {len(img_urls) - success_count} 张')
def main():
"""主函数:执行图片爬取、提取和下载流程"""
url = 'https://news.fzu.edu.cn/yxfd.htm'
save_dir = 'downloaded_images'
print('正在获取网页...')
html = get_html_text(url)
if not html:
return
print('正在提取图片链接...')
img_urls = extract_image_urls(html, url)
print(f'共找到 {len(img_urls)} 个图片链接')
print('开始下载图片...')
download_images(img_urls, save_dir)
if __name__ == '__main__':
main()
输出结果
运行代码后,所有符合条件的图片均成功保存在本地 downloaded_images 文件夹中。


2)、心得体会
本作业的关键在于正则表达式对图片链接的提取和相对路径的处理。图片 src 属性可能是绝对路径、协议相对路径或相对路径,需要分别处理才能生成完整的可请求URL。
在实际运行中,我发现抓取的结果既包含新闻正文中的大图,也包含了网站的 Logo 和导航图标。通过重新审视作业要求“爬取所有 JPEG、JPG 或 PNG 格式图片文件”,我确认这些图标完全符合抓取标准。同时,我注意到网页中存在大量 SVG 矢量图,因此在正则中使用了 (?:jpg|jpeg|png) 进行严格过滤,成功排除了 SVG 文件。
通过本次实践,我掌握了“请求→解析→下载→保存”这一图片爬取的完整流程,也理解了文件 I/O 操作中编码模式(二进制写入 'wb')选择的重要性。

浙公网安备 33010602011771号