数据采集实践第三次作业
作业①
要求:指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。使用scrapy框架分别实现单线程和多线程的方式爬取。
–务必控制总页数(学号尾数2位)、总下载的图片数量(尾数后3位)等限制爬取的措施。
1.作业内容
点击查看代码
from bs4 import BeautifulSoup
from bs4 import UnicodeDammit
import urllib.request
import sqlite3
class WeatherDB:
def openDB(self):
self.con=sqlite3.connect("weathers.db")
self.cursor=self.con.cursor()
try:
self.cursor.execute("create table weathers (wCity varchar(16),wDate varchar(16),wWeather varchar(64),wTemp varchar(32),constraint pk_weather primary key (wCity,wDate))")
except:
self.cursor.execute("delete from weathers")
def closeDB(self):
self.con.commit()
self.con.close()
def insert(self, city, date, weather, temp):
pass
def insert(self, city, date, weather, temp):
try:
self.cursor.execute("insert into weathers (wCity,wDate,wWeather,wTemp) values (?,?,?,?)",
(city, date, weather, temp))
except Exception as err:
print(err)
def show(self):
self.cursor.execute("select * from weathers")
rows = self.cursor.fetchall()
print("%-16s%-16s%-32s%-16s" % ("city", "date", "weather", "temp"))
for row in rows:
print("%-16s%-16s%-32s%-16s" % (row[0], row[1], row[2], row[3]))
class WeatherForecast:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre"}
self.cityCode = {"北京": "101010100", "上海": "101020100", "广州": "101280101", "深圳": "101280601"}
def forecastCity(self, city):
if city not in self.cityCode.keys():
print(city + " code cannot be found")
return
url = "http://www.weather.com.cn/weather/" + self.cityCode[city] + ".shtml"
try:
req = urllib.request.Request(url, headers=self.headers)
data = urllib.request.urlopen(req)
data = data.read()
dammit = UnicodeDammit(data, ["utf-8", "gbk"])
data = dammit.unicode_markup
soup = BeautifulSoup(data, "lxml")
lis = soup.select("ul[class='t clearfix'] li")
for li in lis:
try:
date=li.select('h1')[0].text
weather=li.select('p[class="wea"]')[0].text
temp_span = li.select('p[class="tem"] span')
temp_i = li.select('p[class="tem"] i')
temp = (temp_span[0].text if temp_span else '') + "/" + (temp_i[0].text if temp_i else '')
print(city,date,weather,temp)
self.db.insert(city,date,weather,temp)
except Exception as err:
print(err)
except Exception as err:
print(err)
def process(self, cities):
self.db = WeatherDB()
self.db.openDB()
for city in cities:
self.forecastCity(city)
# self.db.show()
self.db.closeDB()
ws = WeatherForecast()
ws.process(["北京", "上海", "广州", "深圳"])
print("completed")
2.心得体会
在处理网页数据抓取任务时,我们不可避免地会面临网页结构变化带来的挑战。这次实验让我深刻认识到编写灵活且健壮的代码的重要性,尤其是在面对可能随时变化的网页结构时。实验初期,我们遇到了由于HTML结构修改导致的选择器失效问题,特别是对于温度信息的提取部分。这不仅突显了网页结构的不稳定性,也强调了选择器设计的灵活性。在编写选择器时,我们应尽量避免硬编码具体的标签或类名,而是采用更通用的选择器或结合多个选择器来提高匹配的准确性。
温度信息可能会出现的不同
作业②
要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取股票相关信息。
1.作业内容
点击查看代码
import requests
import sqlite3
import json
import logging
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 数据库操作类
class StockDatabase:
def __init__(self, db_name):
self.conn = sqlite3.connect(db_name)
self.create_table()
logging.info(f"Database '{db_name}' connected.")
def create_table(self):
"""创建存储股票信息的表格"""
c = self.conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS stock_info (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_code TEXT,
stock_name TEXT,
latest_price REAL,
change_percent REAL,
change_amount REAL,
volume TEXT,
turnover TEXT,
amplitude REAL,
high REAL,
low REAL,
open_price REAL,
yesterday_close REAL
)
''')
self.conn.commit()
logging.info("Table 'stock_info' is ready.")
def save_stock_data(self, stock_data):
"""将股票数据保存到数据库"""
c = self.conn.cursor()
for stock in stock_data:
stock_record = (
stock.get('f12'), # 股票代码
stock.get('f14'), # 股票名称
stock.get('f2'), # 最新报价
stock.get('f3'), # 涨跌幅
stock.get('f4'), # 涨跌额
stock.get('f5'), # 成交量
stock.get('f6'), # 成交额
stock.get('f7'), # 振幅
stock.get('f15'), # 最高价
stock.get('f16'), # 最低价
stock.get('f17'), # 今开
stock.get('f18') # 昨收
)
try:
c.execute('''
INSERT INTO stock_info
(stock_code, stock_name, latest_price, change_percent, change_amount, volume, turnover, amplitude, high, low, open_price, yesterday_close)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', stock_record)
logging.info(f"Stock data for '{stock.get('f14')}' saved.")
except Exception as e:
logging.error(f"Error saving stock data: {e}")
self.conn.commit()
def display_stock_data(self):
"""显示数据库中的股票数据"""
c = self.conn.cursor()
c.execute("SELECT * FROM stock_info")
rows = c.fetchall()
# 打印表头
print(f"{'序号':<5} {'股票代码':<10} {'股票名称':<10} {'最新报价':<10} {'涨跌幅':<10} {'涨跌额':<10} {'成交量':<10} {'成交额':<15} {'振幅':<10} {'最高':<10} {'最低':<10} {'今开':<10} {'昨收':<10}")
# 打印每行数据
for row in rows:
latest_price = float(row[3]) if row[3] not in ('-', None) else 0.0
change_percent = float(row[4]) if row[4] not in ('-', None) else 0.0
change_amount = float(row[5]) if row[5] not in ('-', None) else 0.0
amplitude = float(row[8]) if row[8] not in ('-', None) else 0.0
high = float(row[9]) if row[9] not in ('-', None) else 0.0
low = float(row[10]) if row[10] not in ('-', None) else 0.0
open_price = float(row[11]) if row[11] not in ('-', None) else 0.0
yesterday_close = float(row[12]) if row[12] not in ('-', None) else 0.0
print(f"{row[0]:<5} {row[1]:<10} {row[2]:<10} "
f"{latest_price:<10.2f} {change_percent:<10.2f} {change_amount:<10.2f} "
f"{row[6]:<10} {row[7]:<15} {amplitude:<10.2f} {high:<10.2f} "
f"{low:<10.2f} {open_price:<10.2f} {yesterday_close:<10.2f}")
def close_connection(self):
"""关闭数据库连接"""
self.conn.close()
logging.info("Database connection closed.")
# 获取股票数据
def get_stock_data():
url = 'https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery112409840494931556277_1633338445629&pn=1&pz=10&po=1&np=1&fltt=2&invt=2&fid=f3&fs=b:MK0021&fields=f12,f14,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f18,f15,f16,f17,f23'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0'
}
response = requests.get(url, headers=headers)
# 去除不必要的字符,提取有效的JSON部分
response_text = response.text.split('(', 1)[1].rsplit(')', 1)[0]
stock_data = json.loads(response_text)['data']['diff'] # 解析JSON并提取有用的字段
return stock_data
# 主函数
def main():
# 创建数据库连接
db = StockDatabase('eastmoney_stock.db')
# 获取股票数据
stock_data = get_stock_data()
# 保存数据到数据库
db.save_stock_data(stock_data)
# 显示表格数据
db.display_stock_data()
# 关闭数据库连接
db.close_connection()
if __name__ == '__main__':
main()
2.心得体会
我使用requests和BeautifulSoup库来爬取股票数据,并将其存储在SQLite数据库中。这次经历让我深刻体会到网络爬虫的复杂性和数据处理的重要性。编写爬虫时,我必须仔细分析网页结构,并编写能够适应变化的代码。处理数据时,我学会了如何高效地解析和清洗数据,并将其存储在数据库中,以便后续的查询和分析。此外,实验还让我意识到异常处理和日志记录的重要性,它们对于调试和维护爬虫程序至关重要。
作业③
要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。
1.作业内容
点击查看代码
import urllib.request
from bs4 import BeautifulSoup
import sqlite3
# 目标网址
url = "https://www.shanghairanking.cn/rankings/bcur/2021"
# 使用BeautifulSoup解析HTML内容
resp = urllib.request.urlopen(url)
html = resp.read()
soup = BeautifulSoup(html, 'html.parser')
# 找到包含排名信息的表格行
rows = soup.find_all('tr')
# 打印标题
print(f"排名\t学校名称{'':<6}\t省市{'':<1}\t学校类型\t总分")
# 连接到SQLite数据库(如果不存在则创建)
conn = sqlite3.connect('rank2021.db')
cursor = conn.cursor()
# 创建表
cursor.execute('''
CREATE TABLE IF NOT EXISTS university_rank (
rank TEXT,
uni_name TEXT,
province TEXT,
uni_type TEXT,
score TEXT
)
''')
# 遍历表格的每一行,提取信息并保存到数据库
for row in rows:
# 找到排名
rank = row.find('td').text.strip() if row.find('td') else ""
# 找到学校名称的span
span = row.find('span', class_="name-cn")
uni_name = span.text.strip() if span else ""
# 找到剩余内容
province = row.find_all('td')[2].text.strip() if row.find('td') else ""
uni_type = row.find_all('td')[3].text.strip() if row.find('td') else ""
score = row.find_all('td')[4].text.strip() if row.find('td') else ""
# 打印提取的信息
print(f"{rank}\t{uni_name:<10}\t{province:<3}\t{uni_type:<4}\t{score}")
# 插入数据到数据库
cursor.execute('''
INSERT INTO university_rank (rank, uni_name, province, uni_type, score)
VALUES (?, ?, ?, ?, ?)
''', (rank, uni_name, province, uni_type, score))
# 提交事务
conn.commit()
# 关闭数据库连接
conn.close()
2.心得体会
本次实验中我分析了目标网页的结构,确定了需要爬取的数据字段,包括学校排名、名称、类型、所在地、总分以及各个评价指标等。然后,我编写了爬虫脚本,,提取所需的数据。在数据提取过程中,我特别注意了数据的清洗和转换,确保数据的准确性和一致性。我将提取的数据存储到SQLite数据库中。创建了一个数据库和相应的表结构,并编写了插入数据的SQL语句。在数据存储过程中,我同样注重了数据的完整性和错误处理,确保即使在遇到异常情况时,程序也能优雅地处理错误并继续执行。为了记录整个分析过程,我使用了浏览器的开发者工具(F12)来调试和查看网络请求。我录制了分析过程,并使用工具将录制的视频转换为GIF图片。这样不仅方便了博客的读者理解整个爬虫的工作流程,也使得博客内容更加生动和直观。通过这次实验,我深刻体会到了网络爬虫技术在数据获取中的重要性,以及数据处理和存储的复杂性。我学会了如何分析网页结构,编写高效的爬虫脚本,以及如何将数据存储到数据库中。此外,我也认识到了数据清洗和错误处理的重要性,它们是保证数据质量和程序稳定性的关键。