数据采集第一次作业
Python网络爬虫作业
作业①:大学排名数据爬取
一、代码与运行结果
要求:用requests和BeautifulSoup库方法定向爬取给定网址( http://www.shanghairanking.cn/rankings/bcur/2020 )。
完整代码:
"""
作业①: 爬取2020年中国大学排名
使用requests和BeautifulSoup库
目标网址: http://www.shanghairanking.cn/rankings/bcur/2020
输出: 排名、学校名称、省市、学校类型、总分
"""
import requests
from bs4 import BeautifulSoup
URL = "http://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/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
}
def fetch_page(url):
"""
使用requests发送HTTP请求获取页面内容
"""
try:
response = requests.get(url, headers=HEADERS, timeout=30)
response.encoding = response.apparent_encoding
if response.status_code == 200:
return response.text
else:
print("请求失败,状态码: %d" % response.status_code)
return None
except Exception as e:
print("请求异常: %s" % e)
return None
def parse_university_data(html):
"""
使用BeautifulSoup解析大学排名表格
提取: 排名、学校名称、省市、学校类型、总分
"""
if not html:
return []
soup = BeautifulSoup(html, "html.parser")
table = soup.find("table", class_="rk-table")
if table is None:
print("未找到排名表格")
return []
universities = []
tbody = table.find("tbody")
if tbody:
rows = tbody.find_all("tr")
for row in rows:
tds = row.find_all("td")
if len(tds) < 5:
continue
name_span = tds[1].find("span", class_="name-cn")
universities.append({
"rank": tds[0].get_text(strip=True),
"name": name_span.get_text(strip=True) if name_span else "",
"province": tds[2].get_text(strip=True),
"category": tds[3].get_text(strip=True),
"score": tds[4].get_text(strip=True),
})
return universities
def print_universities(universities):
"""
格式化打印大学排名信息
"""
if not universities:
print("未获取到数据")
return
print("\n2020 软科中国大学排名")
print("-" * 78)
print("排名\t" + "学校名称\t" + "省市\t" + "类型\t" + "总分")
print("-" * 78)
for r in universities:
print(r["rank"] + "\t" + r["name"] + "\t" + r["province"]
+ "\t" + r["category"] + "\t" + r["score"])
print("-" * 78)
print("共 %d 条" % len(universities))
def main():
print("正在爬取:%s" % URL)
html = fetch_page(URL)
if html:
universities = parse_university_data(html)
print("页面表格解析到 %d 条记录" % len(universities))
print_universities(universities)
else:
print("页面获取失败")
if __name__ == "__main__":
main()
运行结果截图:

二、作业心得
本次作业主要学习了如何使用requests库发送HTTP请求和BeautifulSoup库解析HTML文档。
关键技术点:
-
requests库的使用:通过
requests.get()方法发送GET请求,设置headers模拟浏览器访问,避免被反爬虫机制拦截。使用timeout参数设置超时时间,防止请求长时间挂起。 -
网页编码处理:使用
response.apparent_encoding自动检测网页编码,而不是硬编码为utf-8,避免中文乱码问题。 -
BeautifulSoup解析HTML:使用
BeautifulSoup(html, "html.parser")创建解析器对象,通过find()和find_all()方法定位HTML元素。find()返回第一个匹配的元素,find_all()返回所有匹配的元素列表。 -
数据提取技巧:使用
get_text(strip=True)方法获取元素文本并自动去除首尾空白字符。对于嵌套元素,可以链式调用find()方法逐层定位。
遇到的问题与解决:
- 初始运行时发现中文显示为乱码,后来发现需要使用
response.apparent_encoding自动检测网页编码,而不是硬编码为utf-8。 - 表格数据解析时,需要注意HTML结构中可能存在空行或格式不一致的情况,通过判断
td标签数量来过滤无效行。
收获:掌握了网页结构分析的基本方法,学会了使用浏览器开发者工具查看HTML源码,理解了DOM树的结构和遍历方式。
作业②:商城商品比价爬虫
一、代码与运行结果
要求:用requests和re库方法设计某个商城商品比价定向爬虫,爬取该商城以关键词"书包"搜索页面的数据,爬取商品名称和价格。
完整代码:
"""
作业②: 商城商品比价定向爬虫
使用requests和re库
目标商城: 当当网
爬取关键词"书包"的商品名称和价格
输出: 序号、价格、商品名
"""
import requests
import re
import time
from urllib.parse import quote
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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
}
def fetch_page(url):
"""
使用requests发送HTTP请求获取页面内容
"""
try:
response = requests.get(url, headers=HEADERS, timeout=30)
try:
html = response.content.decode("utf-8")
except UnicodeDecodeError:
html = response.content.decode("gbk")
if response.status_code == 200:
return html
else:
print("请求失败,状态码: %d" % response.status_code)
return None
except Exception as e:
print("请求异常: %s" % e)
return None
def parse_products(html):
"""
使用正则表达式解析商品名称和价格
"""
if not html:
return []
products = []
# 匹配商品名称(从title属性提取)
name_pattern = r'<p[^>]*class="name"[^>]*>\s*<a[^>]*title="([^"]*)"'
# 匹配价格
price_pattern = r'<span[^>]*class="price_n"[^>]*>¥\s*([0-9]+\.?[0-9]*)</span>'
# 查找所有名称
names = re.findall(name_pattern, html, re.DOTALL)
# 查找所有价格
prices = re.findall(price_pattern, html, re.DOTALL)
# 合并名称和价格
for i in range(min(len(names), len(prices))):
name = names[i].strip()
price = prices[i].strip()
if name and price:
products.append({
"name": name,
"price": price,
})
return products
def scrape_dangdang(keyword="书包", max_items=60):
"""
爬取当当网商品数据
"""
print("=" * 60)
print("开始爬取当当网 - 关键词: '%s'" % keyword)
print("目标爬取数量: %d 项" % max_items)
print("=" * 60)
all_products = []
page = 1
items_per_page = 60
while len(all_products) < max_items:
encoded_keyword = quote(keyword)
url = "http://search.dangdang.com/?key=%s&act=input&page_index=%d" % (encoded_keyword, page)
print("\n正在爬取第 %d 页..." % page)
print("URL: %s" % url)
html = fetch_page(url)
if html is None:
print("获取页面失败,停止爬取")
break
products = parse_products(html)
if not products:
print("未找到商品数据,可能已到达最后一页")
break
print("本页找到 %d 件商品" % len(products))
for product in products:
if len(all_products) >= max_items:
break
all_products.append(product)
page += 1
# 礼貌延迟,避免请求过快
time.sleep(1)
if len(products) < items_per_page:
print("已到达最后一页")
break
return all_products
def display_products(products):
"""
格式化显示商品信息
"""
print("\n" + "=" * 80)
print("爬取结果汇总 - 共 %d 件商品" % len(products))
print("=" * 80)
print("%-6s %-50s %-10s" % ("序号", "商品名称", "价格(元)"))
print("-" * 80)
for idx, product in enumerate(products, 1):
name = product["name"]
price = product["price"]
if len(name) > 48:
name = name[:45] + "..."
print("%-6d %-50s ¥%-10s" % (idx, name, price))
print("=" * 80)
if products:
prices = [float(p["price"]) for p in products if p["price"].replace(".", "").isdigit()]
if prices:
print("\n价格统计:")
print(" 最低价: ¥%.2f" % min(prices))
print(" 最高价: ¥%.2f" % max(prices))
print(" 平均价: ¥%.2f" % (sum(prices) / len(prices)))
def save_to_file(products, filename="书包_商品信息.txt"):
"""
保存商品信息到文件
"""
with open(filename, "w", encoding="utf-8") as f:
f.write("书包商品信息 - 共%d件\n" % len(products))
f.write("=" * 80 + "\n")
f.write("%-6s %-50s %-10s\n" % ("序号", "商品名称", "价格(元)"))
f.write("-" * 80 + "\n")
for idx, product in enumerate(products, 1):
f.write("%-6d %-50s ¥%-10s\n" % (idx, product["name"], product["price"]))
f.write("=" * 80 + "\n")
print("\n数据已保存到: %s" % filename)
def main():
keyword = "书包"
max_items = 60
products = scrape_dangdang(keyword, max_items)
if products:
display_products(products)
save_to_file(products)
else:
print("未获取到任何商品数据")
if __name__ == "__main__":
main()
运行结果截图:

二、作业心得
本次作业重点学习了正则表达式在网页数据提取中的应用。
关键技术点:
-
正则表达式基础:
r'...':原始字符串,避免反斜杠转义问题[^>]*:匹配任意数量的非>字符([^"]*):捕获组,提取引号内的内容re.DOTALL:使.匹配包括换行符在内的所有字符
-
URL编码:使用
urllib.parse.quote()对中文关键词进行URL编码,确保URL格式正确。 -
分页爬取:通过循环递增
page_index参数实现多页数据爬取,使用max_items控制爬取总量。 -
编码处理:当当网使用GBK编码,通过
response.content.decode("utf-8")尝试解码,失败时回退到gbk解码,避免中文乱码。 -
礼貌爬取:使用
time.sleep(1)设置请求间隔,避免对目标服务器造成过大压力。
正则表达式解析:
-
商品名称正则:
<p[^>]*class="name"[^>]*>\s*<a[^>]*title="([^"]*)"- 匹配
<p>标签中class包含"name"的元素 - 查找其子元素
<a>标签的title属性 - 捕获引号内的商品名称
- 匹配
-
价格正则:
<span[^>]*class="price_n"[^>]*>¥\s*([0-9]+\.?[0-9]*)</span>- 匹配
<span>标签中class为"price_n"的元素 - 提取
¥(¥符号)后的数字 - 支持整数和小数格式
- 匹配
遇到的问题与解决:
- 中文关键词直接拼接到URL会导致编码错误,需要使用
quote()函数进行URL编码。 - 部分商品页面结构可能不一致,通过判断名称和价格列表长度取最小值来保证数据完整性。
- 网页编码问题:当当网使用GBK编码,需要先尝试utf-8解码,失败时回退到gbk解码,否则中文会显示为乱码。
- 正则表达式编写需要仔细分析HTML源码结构,使用浏览器开发者工具查看元素很方便。
收获:掌握了正则表达式的基本语法和常用模式,理解了正则表达式与BeautifulSoup的适用场景差异。正则表达式适合提取结构简单、规律性强的数据,而BeautifulSoup更适合处理复杂的DOM结构。
作业③:网页图片爬取
一、代码与运行结果
要求:爬取给定网页( https://news.fzu.edu.cn/yxfd.htm )的所有JPEG、JPG或PNG格式图片文件,保存在一个文件夹中。
完整代码:
"""
作业③: 爬取网页图片
使用requests和re库
目标网址: https://news.fzu.edu.cn/yxfd.htm
输出: 将所有JPEG、JPG、PNG格式图片保存到指定文件夹
"""
import os
import re
import requests
from urllib.parse import urljoin
def fetch_page(url):
"""
使用requests发送HTTP请求获取页面内容
"""
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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
}
try:
response = requests.get(url, headers=headers, timeout=30)
response.encoding = response.apparent_encoding
if response.status_code == 200:
return response.text
else:
print("请求失败,状态码: %d" % response.status_code)
return None
except Exception as e:
print("请求异常: %s" % e)
return None
def extract_image_urls(html, base_url):
"""
使用正则表达式提取页面中所有JPEG、JPG、PNG格式图片的URL
"""
if not html:
return []
# 匹配img标签中的src属性,支持jpg、jpeg、png格式
img_pattern = r'<img[^>]+src=["\']([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG))["\']'
# 查找所有匹配的图片URL
relative_urls = re.findall(img_pattern, html, re.IGNORECASE)
# 将相对路径转换为绝对路径
absolute_urls = []
for url in relative_urls:
absolute_url = urljoin(base_url, url)
absolute_urls.append(absolute_url)
return absolute_urls
def download_images(image_urls, save_dir):
"""
下载图片并保存到指定文件夹
"""
if not image_urls:
print("未找到图片URL")
return 0
# 创建保存目录
os.makedirs(save_dir, exist_ok=True)
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",
}
downloaded_count = 0
for i, img_url in enumerate(image_urls, 1):
try:
print("正在下载第 %d 张图片: %s" % (i, img_url))
response = requests.get(img_url, headers=headers, timeout=30)
if response.status_code == 200:
# 从URL中提取文件名,如果没有则使用序号命名
filename = img_url.split("/")[-1].split("?")[0]
if not filename or not re.search(r'\.(jpg|jpeg|png)$', filename, re.IGNORECASE):
ext = img_url.split(".")[-1].split("?")[0].lower()
if ext not in ["jpg", "jpeg", "png"]:
ext = "jpg"
filename = "image_%d.%s" % (i, ext)
filepath = os.path.join(save_dir, filename)
# 如果文件名重复,添加序号
if os.path.exists(filepath):
name, ext = os.path.splitext(filename)
filepath = os.path.join(save_dir, "%s_%d%s" % (name, i, ext))
with open(filepath, "wb") as f:
f.write(response.content)
print(" 已保存: %s" % filename)
downloaded_count += 1
else:
print(" 下载失败,状态码: %d" % response.status_code)
except Exception as e:
print(" 下载异常: %s" % e)
return downloaded_count
def main():
url = "https://news.fzu.edu.cn/yxfd.htm"
save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fzu_images")
print("=" * 70)
print("开始爬取网页图片: %s" % url)
print("图片保存目录: %s" % save_dir)
print("=" * 70)
# 获取页面内容
print("\n正在获取页面内容...")
html = fetch_page(url)
if not html:
print("页面获取失败")
return
# 提取图片URL
print("正在提取图片URL...")
image_urls = extract_image_urls(html, url)
print("共找到 %d 张图片" % len(image_urls))
if not image_urls:
print("未找到图片")
return
# 下载图片
print("\n开始下载图片...")
count = download_images(image_urls, save_dir)
print("\n" + "=" * 70)
print("下载完成! 共下载 %d/%d 张图片" % (count, len(image_urls)))
print("保存位置: %s" % save_dir)
print("=" * 70)
if __name__ == "__main__":
main()
运行结果截图:

下载的图片文件夹:


二、作业心得
本次作业学习了如何从网页中提取并下载图片文件。
关键技术点:
-
图片URL提取:使用正则表达式
<img[^>]+src=["\']([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG))["\']匹配<img>标签中的src属性,并通过re.IGNORECASE实现大小写不敏感匹配。 -
相对路径转换:网页中的图片URL可能是相对路径,使用
urllib.parse.urljoin(base_url, relative_url)将相对路径转换为绝对路径,确保可以正确访问图片资源。 -
二进制文件下载:图片是二进制文件,使用
requests.get()获取响应后,通过response.content获取二进制数据(而非response.text),以"wb"(写入二进制)模式保存到本地文件。 -
文件命名与去重:从URL中提取文件名,处理URL参数(
?后面的内容)。如果文件名重复,添加序号避免覆盖。 -
目录创建:使用
os.makedirs(save_dir, exist_ok=True)创建保存目录,exist_ok=True参数确保目录已存在时不会报错。
正则表达式解析:
<img[^>]+:匹配<img标签开始,后跟至少一个非>字符src=["\']:匹配src=后的引号(单引号或双引号)([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG)):捕获组,匹配以图片扩展名结尾的URL(?:...):非捕获组,仅用于分组
["\']:匹配结束引号
遇到的问题与解决:
- 部分图片URL是相对路径,直接使用无法访问,需要使用
urljoin()转换为绝对路径。 - 文件名可能包含URL参数(如
?v=123),需要使用split("?")[0]去除参数部分。 - 下载二进制文件时必须使用
response.content而非response.text,否则会导致文件损坏。 - 部分图片文件名相同,需要添加序号避免覆盖。
收获:掌握了网页图片提取和下载的完整流程,理解了相对路径与绝对路径的区别,学会了处理二进制文件的正确方法。同时认识到网络爬虫需要遵守网站的robots.txt协议,合理控制请求频率,避免对目标服务器造成过大压力。
总结
通过本次作业,系统学习了Python网络爬虫的三种常用技术组合:
- requests + BeautifulSoup:适合解析结构化的HTML表格和DOM树
- requests + re:适合提取规律性强的文本数据,如价格、名称等
- requests + re + 文件操作:适合批量下载网页资源,如图片、文件等
核心技能提升:
- 熟练使用
requests库发送HTTP请求和处理响应 - 掌握
BeautifulSoup的find()、find_all()、get_text()等常用方法 - 理解正则表达式的基本语法和常用模式
- 学会使用浏览器开发者工具分析网页结构
- 了解网络爬虫的基本规范和注意事项
注意事项:
- 设置合理的
User-Agent请求头 - 正确处理网页编码(
response.apparent_encoding或response.content.decode()) - 添加异常处理机制
- 控制请求频率,避免对服务器造成压力
- 遵守网站的
robots.txt协议 - 仅将爬虫技术用于学习和合法用途

浙公网安备 33010602011771号