数据采集第一次作业
作业①:使用requests+BeautifulSoup爬取软科中国大学排名
1.1 实验代码与运行截图
先去目标网址check一下

不错,列表很整齐一个个元素的

里面也确实是我们要的信息,开爬

奇怪,只有30条。查看发现一面就是30条,需要到第二面,检查按钮,发现不是请求新的html,只能尝试点击2什么的,说是要用selenium,但是我觉得应该暂时用不到这么麻烦的

用network排查Ajax,发现也不是一般ajax

科软你这家伙,好像有点超出我的知识储备了。请出搜索大法:搜一个排名40的兰州大学
是在一个payload.js里面,而且在页面刷新还没点2的时候就有了,好多payload.js

终于定位到原payload.js,不负人工寻找,这个里面包含了所有排名信息。其实它就是一个json

搞它,掏url,请求后py解析即可

代码
👉 点击查看代码
import urllib.request
import json
import re
# 浏览器网络面板中抓到的排名数据文件(JavaScript,不是 JSON)
url = "https://www.shanghairanking.cn/_nuxt/static/1789974610/rankings/bcur/2020/payload.js"
# 模拟浏览器访问
request = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/140.0.0.0 Safari/537.36",
"Referer": "https://www.shanghairanking.cn/rankings/bcur/2020",
}
)
# 获取数据
with urllib.request.urlopen(request, timeout=30) as response:
text = response.read().decode("utf-8")
# payload.js 用函数参数压缩重复值,例如 province:p,其中 p 对应“北京”。
# 读取参数名和文件末尾传入的值,建立对应关系,不执行 JavaScript。
parameters = re.search(r"function\(([^)]*)\)", text)
arguments = re.search(r"\}\((.*)\)\)\);\s*$", text, re.S)
names = parameters.group(1).split(",")
values = json.loads("[" + arguments.group(1) + "]")
variables = dict(zip(names, values))
# 每所大学的基本字段位于 univUp 和 indData 之间。
# 字段值可能是字符串、数字,也可能是上面定义的变量名。
token = r'"(?:\\.|[^"\\])*"|[A-Za-z_$][\w$]*|-?\d+(?:\.\d+)?'
schools = []
for match in re.finditer(r"\{univUp:.*?,indData:", text, re.S):
school = {}
for field in ["ranking", "univNameCn", "province", "univCategory", "score"]:
result = re.search(r"(?:\{|,)" + field + r":(" + token + r")(?=,)", match.group())
value = result.group(1)
school[field] = variables[value] if value in variables else json.loads(value)
schools.append(school)
# 打印表头
print(f"{'排名':<6}{'学校名称':<18}{'省市':<10}{'学校类型':<10}{'总分':>8}")
print("-" * 60)
# 输出前36名
for school in schools[:36]:
rank = school["ranking"]
name = school["univNameCn"]
province = school["province"]
school_type = school["univCategory"]
score = school["score"]
print(f"{rank:<6}{name:<18}{province:<10}{school_type:<10}{score:>8}")
结果
成功!输出36个

1.2 心得体会
很神奇的一个过程。
怎么会把数据都写在那些payload.js里啊,而且甚至很多payload.js连名字都一毛一样但是内容完全不同。
发现原来其实网站的设计很复杂,很多东西其实我见都没见过。
然后解析这个js也是挺麻烦的。
作业②:使用requests+re正则实现商城商品比价爬虫(关键词:书包)
2.1 实验代码与运行截图
商城选定与网页勘探
爬当当网,因为不用登录
研究后发现其翻页方式就是,从url上面修改page_index参数(但是没实践


商品列表的定位:

商品名称title:

商品价格price_n:

代码
👉 点击查看代码
"""作业二:用 requests 和 re 爬取当当网的书包名称和价格。"""
import csv
import re
from html import unescape
from pathlib import Path
from urllib.parse import quote
import requests
# 当当搜索使用 GBK 编码的关键词。
keyword = "书包"
url = "https://search.dangdang.com/?key=" + quote(keyword, encoding="gbk") + "&act=input"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
response.encoding = "gbk"
# 先定位商品列表,再分割商品条目,保证名称和售价属于同一商品。
product_list = re.search(
r'<ul\b[^>]*class="bigimg\b[^"]*"[^>]*>(.*?)</ul>', response.text, re.S
).group(1)
items = re.findall(r'<li\b[^>]*\bid="\d+"[^>]*>(.*?)</li>', product_list, re.S)
products = []
for item in items:
name_html = re.search(
r'<a\b[^>]*name="itemlist-title"[^>]*>(.*?)</a>', item, re.S
).group(1)
# 去掉搜索关键词上的高亮标签,并还原 HTML 字符实体。
name = unescape(re.sub(r"<[^>]+>", "", name_html)).strip()
price = re.search(
r'<span\b[^>]*class="price_n"[^>]*>\s*[¥¥]\s*([\d.]+)', unescape(item)
).group(1)
products.append([float(price), name])
# 按售价从低到高排列,便于比较当前搜索页中的商品价格。
products.sort(key=lambda product: product[0])
print("序号 价格(元) 商品名")
for number, (price, name) in enumerate(products, 1):
print(f"{number:<6}{price:<12.2f}{name}")
print(f"\n关键词:{keyword};共爬取搜索首页的 {len(products)} 件商品。")
result_dir = Path(__file__).resolve().parent / "results"
result_dir.mkdir(exist_ok=True)
with (result_dir / "schoolbags.csv").open("w", encoding="utf-8-sig", newline="") as file:
writer = csv.writer(file)
writer.writerow(["序号", "价格", "商品名"])
for number, (price, name) in enumerate(products, 1):
writer.writerow([number, f"{price:.2f}", name])
结果


2.2 心得体会
嗯,当当网被我爬了
(没有去把登录完成)
要求是使用re去做,说实话并不好用。感觉容错比较差,自己定搜寻标签也蛮麻烦的。
网页上显示的是人民币符号,源码里却写成 ¥,正则也得跟着源码的写法来。
作业③:爬取网页所有jpg/jpeg/png图片并保存到本地文件夹
3.1 实验代码与运行截图
页面勘探
查看网站后发现有页面之分,既然要求是所有图片,那么7面的网站我们也来找全部吧
首页是没有后缀的

第5面后缀为3

第7面也是最后1一面后缀为1,可知逆序

总页面数,可以看到是标签为p_no的元素,可以由此确定。

然后图片的话,就用img选定就可以了。类型后面再过滤。
代码
👉 点击查看代码
"""作业三:爬取影像福大全部分页的图片。"""
from pathlib import Path
from urllib.parse import urljoin, urlsplit
import requests
from bs4 import BeautifulSoup
# 获取首页
url = "https://news.fzu.edu.cn/yxfd.htm"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)
response.encoding = "utf-8"
soup = BeautifulSoup(response.text, "html.parser")
# 从分页区读取总页数
pagination = soup.select_one(".n_right .pb_sys_common .p_pages")
page_numbers = pagination.select(".p_no a, .p_no_d")
total_pages = max(int(page.get_text()) for page in page_numbers)
# 生成全部页面地址
page_urls = [url]
for number in range(total_pages - 1, 0, -1):
page_urls.append(urljoin(url, f"yxfd/{number}.htm"))
# 逐页提取图片地址
image_urls = []
for page_number, page_url in enumerate(page_urls, 1):
response = requests.get(page_url, headers=headers)
response.encoding = "utf-8"
soup = BeautifulSoup(response.text, "html.parser")
print(f"第 {page_number} 页:{page_url}")
for image in soup.find_all("img"):
image_url = urljoin(page_url, image["src"])
extension = Path(urlsplit(image_url).path).suffix.lower()
if extension in (".jpg", ".jpeg", ".png") and image_url not in image_urls:
image_urls.append(image_url)
# 下载图片
image_dir = Path("images")
image_dir.mkdir(exist_ok=True)
for number, image_url in enumerate(image_urls, 1):
extension = Path(urlsplit(image_url).path).suffix.lower()
filename = f"{number:03d}{extension}"
response = requests.get(image_url, headers=headers)
(image_dir / filename).write_bytes(response.content)
print(f"已保存:{filename}")
print(f"共爬取 {total_pages} 页,保存 {len(image_urls)} 张图片。")
结果截图


3.2 心得体会
我原来只抓了首页,后来按分页区的页数把后面的页面也加上了。这里的链接有点绕:第二页是 6.htm,第五页是 3.htm,最后一页才是 1.htm。首页还把第六页的链接省略了,所以我先生成了全部 7 个地址,再逐页去抓。
翻页以后,图片路径也要跟着当前页来拼。我用了 urljoin(),不用自己数路径里该加几个斜杠。提取地址时只留下 .jpg、.jpeg、.png,后缀先转成小写。各页都有一样的标志和页脚图片,地址已经在列表里的就不再加,省得反复下载。
保存图片用的是 response.content,拿到二进制内容后直接写进文件。最后文件夹里有 65 张图片,图片检查也都通过了。比起只看程序打印“已保存”,看到文件能正常打开还是更放心一些。

浙公网安备 33010602011771号