作业①
1.气象网页爬取实验
实验要求
指定一个网站,爬取这个网站中的所有的所有图片,中国气象网(http://www.weather.com.cn)。实现单线程和多线程的方式爬取。
输出信息:将下载的图片保存在images子文件夹中
核心代码
为了成功获取所有的图片链接,我使用 BeautifulSoup 解析 HTML,找到页面上所有 标签,每个标签取出 src,有些是相对路径,所以用 urljoin拼成完整 URL。

website = "http://www.weather.com.cn"
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(website, headers=headers, timeout=10)# 请求网页,获取 HTML 源码
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.text, 'lxml') # 用 lxml 解析器解析 HTML
img_tags = soup.find_all('img')#提取所有 <img> 标签
img_urls = []
# 从每个 <img> 中拿到 src 属性,并拼接成完整的图片 URL
for img in img_tags:
src = img.get('src')
img_url = urljoin(website, src) # 处理相对路径,补成绝对路径
img_urls.append(img_url)
# 使用 set 去重,再转回列表
img_urls = list(set(img_urls))
在实现爬取方式上,我设计了单线程和多线程两种方法,实现不同速度的爬取。
对于单线程,代码实现如下
for img_url in img_urls:
# 从 URL 中取出文件名
filename = os.path.basename(urlparse(img_url).path)
img_path = os.path.join('images', filename)
# 请求图片数据
r = requests.get(img_url, headers=headers, timeout=10)
# 二进制写入本地文件
with open(img_path, 'wb') as f:
f.write(r.content)
print(f"下载完成: {img_url}")
单线程就是简单的按顺序一个一个请求图片。
而对于多线程,代码如下:
threads = []
# 为每一张图片创建一个下载线程
for img_url in img_urls:
# 使用默认参数 u=img_url,避免闭包里 img_url 被后续循环修改
def task(u=img_url):
filename = os.path.basename(urlparse(u).path)
img_path = os.path.join('images', filename)
r = requests.get(u, headers=headers, timeout=10)
with open(img_path, 'wb') as f:
f.write(r.content)
print(f"下载完成: {u}")
t = threading.Thread(target=task)
threads.append(t)
t.start() # 启动线程
# 等待所有线程执行完毕
for t in threads:
t.join()
在多线程中,我定义了task函数,该函数负责下载一张图片并保存到本地,但我为每一张图片都创建了一个,这样就不再是等待一个图片下载好再去下载另外一个,而是转变成了好几个图片开始一起下载,实现多张图片一起发请求。多线程比单线程快速的地方就在于,他不需要花大量时间去等待网络,这会使得资源利用更高效。
实验结果

2.心得体会
从单线程多线程两种方法,我直观的感受到了两种方法在速度上带来的差异,这种差异可能在本次小任务上只差十几秒,但真正到了一个大工程上,这个差异想必非常巨大,我对多线程的实现有了一个更清晰的认识。整个程序从 requests 获取网页源码开始,再用 BeautifulSoup 提取所有 标签,最后通过 urljoin 把相对路径补成完整 URL,最后按 URL 中的文件名保存到本地目录。
作业②
1.股票信息定向爬虫实验
实验要求
熟练掌握 Scrapy 框架中 Item、Pipeline 数据的序列化与持久化输出方法。
掌握 Scrapy + 动态API分析 + SQLite 数据库存储的技术路线,爬取股票相关信息。
爬取东方财富网的股票列表信息,存入本地 stocks.db 数据库。
输出信息:数据库存储格式符合实验要求,表头使用英文命名(如 bStockNo)。
核心代码
在代码中,我依次构造了第1、2、3 页的请求,代码中这些乱七八糟的参数(fs、fid、ut...)是东方财富 API 规定的,直接从 Network 里复制过来就行。接着我定义了parse_api函数,解析 JSON,生成 Item。接着是把每条股票记录封装为 Item,最后把 Item 交给 Scrapy 的 Item Pipeline,后面方便写入数据库。
class EastmoneyStockSpider(scrapy.Spider):
# 爬虫名称(运行:scrapy crawl eastmoney_stock)
name = "eastmoney_stock"
# 限制访问的域名
allowed_domains = ["eastmoney.com", "push2.eastmoney.com", "48.push2.eastmoney.com"]
def start_requests(self):
max_page = 3 # 爬取页数
page_size = 50 # 每页条数
for pn in range(1, max_page + 1):
# 东方财富股票列表接口(分页)
url = (
"http://48.push2.eastmoney.com/api/qt/clist/get"
"?pn={pn}&pz={pz}&po=1&np=1"
"&ut=bd1d9ddb04089700cf9c27f6f7426281"
"&fltt=2&invt=2&wbp2u=|0|0|0|web"
"&fid=f3"
"&fs=m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048"
"&fields=f2,f3,f4,f5,f6,f7,"
"f12,f14,f15,f16,f17,f18"
).format(pn=pn, pz=page_size)
print("请求第", pn, "页 API:", url)
# 发送请求,响应交给 parse_api 处理,并把页码 pn 传过去
yield scrapy.Request(url=url, callback=self.parse_api, meta={'pn': pn})
def parse_api(self, response):
pn = response.meta.get("pn") # 取出当前页码
text = response.text.strip()
# 判断是否为 JSON 格式
if text.startswith("{") or text.startswith("["):
data = json.loads(text)
# diff 是接口里存放股票列表的字段
diff_list = data.get("data", {}).get("diff", [])
self.logger.info("第 %s 页返回 %s 条股票记录", pn, len(diff_list))
# 遍历每一只股票,封装为 Item
for record in diff_list:
item = StockdemoItem()
item["stock_code"] = record.get("f12", "") # 股票代码
item["stock_name"] = record.get("f14", "") # 股票名称
item["latest_price"] = record.get("f2", "") # 最新价
item["change_pct"] = record.get("f3", "") # 涨跌幅
item["change_amt"] = record.get("f4", "") # 涨跌额
item["volume"] = record.get("f5", "") # 成交量
item["turnover"] = record.get("f6", "") # 成交额
item["amplitude"] = record.get("f7", "") # 振幅
item["high_price"] = record.get("f15", "") # 最高
item["low_price"] = record.get("f16", "") # 最低
item["open_price"] = record.get("f17", "") # 今开
item["pre_close"] = record.get("f18", "") # 昨收
# 交给 pipelines 后续处理
yield item
接着看连接数据库代码,这块负责把数据写入数据库
import sqlite3
class SqlitePipeline(object):
def open_spider(self, spider):
"""爬虫启动时:连接数据库并建表"""
self.conn = sqlite3.connect("stocks.db")
self.cursor = self.conn.cursor()
create_table_sql = """
CREATE TABLE IF NOT EXISTS stock_info (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 序号 id
stock_code TEXT, -- 股票代码
stock_name TEXT, -- 股票名称
latest_price TEXT, -- 最新报价
change_pct TEXT, -- 涨跌幅
change_amt TEXT, -- 涨跌额
volume TEXT, -- 成交量
turnover TEXT, -- 成交额
amplitude TEXT, -- 振幅
high_price TEXT, -- 最高
low_price TEXT, -- 最低
open_price TEXT, -- 今开
pre_close TEXT -- 昨收
);
"""
self.cursor.execute(create_table_sql)
self.conn.commit()
def close_spider(self, spider):
"""爬虫结束时:关闭连接"""
self.cursor.close()
self.conn.close()
def process_item(self, item, spider):
"""每拿到一个 item,就插入数据库一行"""
insert_sql = """
INSERT INTO stock_info(
stock_code, stock_name, latest_price,
change_pct, change_amt, volume, turnover,
amplitude, high_price, low_price,
open_price, pre_close
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
"""
data = (
item.get("stock_code"),
item.get("stock_name"),
item.get("latest_price"),
item.get("change_pct"),
item.get("change_amt"),
item.get("volume"),
item.get("turnover"),
item.get("amplitude"),
item.get("high_price"),
item.get("low_price"),
item.get("open_price"),
item.get("pre_close"),
)
print(">>> SqlitePipeline 插入一条记录:", data) # 调试用
self.cursor.execute(insert_sql, data)
self.conn.commit()
return item
实验结果

导出csv文件如图所示
2.心得体会
本任务让我更深刻的了解到如何create 一个 scrapy project项目以及如何连接数据库并写入,理解了 item 从爬取到入库的完整流程,自己写pipeline完成建表和插入,加深了对 Scrapy 数据持久化机制的认识。
作业③
1.爬取外汇数据网站
实验要求
熟练掌握 Scrapy 中 Item、Pipeline 数据的序列化输出方法。
使用 Scrapy 框架 + Xpath + 数据库存储技术路线(本实验采用 SQLite),爬取中国银行外汇牌价网站数据。
候选网站:中国银行外汇牌价 (https://www.boc.cn/sourcedb/whpj/)
输出信息:将爬取的数据存储在数据库中,表头包含:Currency (货币名称), TBP (现汇买入价), CBP (现钞买入价), TSP (现汇卖出价), CSP (现钞卖出价), Time (发布时间)。
核心代码
首先我需要找到主内容区域的第一张表,用 XPath 找到页面中主内容区域的第一个
| 里的文本全部拿出来,接着填充到item里,后面也是交给pipeline准备入库。
入库代码
实验结果
|


浙公网安备 33010602011771号