【数据采集与融合技术】 第二次大作业
作业①:
-
要求:在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。
-
输出信息:
序号 地区 日期 天气信息 温度 1 北京 7日(今天) 晴间多云,北部山区有阵雨或雷阵雨转晴转多云 31℃/17℃ 2 北京 8日(明天) 多云转晴,北部地区有分散阵雨或雷阵雨转晴 34℃/20℃ 3 北京 9日(后台) 晴转多云 36℃/22℃ 4 北京 10日(周六) 阴转阵雨 30℃/19℃ 5 北京 11日(周日) 阵雨 27℃/18℃ ......
1.思路及核心代码
1.1 数据库类
首先设计一个天气数据库类,里面包括打开数据库、关闭数据库、插入数据库和查看数据库内容的方法。
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):
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]))
1.2 天气预报爬取类
WeatherForecast
这个类能够实现爬取目标城市的天气并将其输出,以及将数据写入数据库。
构造函数__init__(self)
初始化请求头和爬取的城市对应的ID。
forecastCity()
方法是这个实现主要功能的函数,能够爬取数据并且解析,将目标城市的天气输出以及将数据写入数据库。
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 = {"福州": "101230101", "上海": "101020100", "广州": "101280101", "深圳": "101280601",
"北京": "101010100", "厦门": "101230201"}
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, "html.parser")
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 = li.select('p[class="tem"] span')[0].text + "/" + li.select('p[class="tem"] i')[0].text
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()
1.3 调用类方法进行爬取
ws = WeatherForecast() # 创建一个WeatherForecast类
ws.process(["福州", "厦门", "北京", "上海", "广州", "深圳"]) # 爬取这几个城市天气数据
print("completed")
1.4 结果
输出控制台的数据
数据库可视化
1.5 码云链接: 作业1
2. 心得体会
在这个作业中,我学会了爬取多个城市的天气数据,并且第一次接触了sqlite
数据库。sqlite
是一款是一种嵌入式数据库,体积小巧,非常适合新手入门学习。
我学会了通过Python
进行数据库的一系列操作,包括创建表、插入表等,受益匪浅。
同时,复习了用面向对象的方法来编程,通过编写各种类来进行爬取数据。
作业②:
-
要求:用requests和BeautifulSoup库方法定向爬取股票相关信息。
-
候选网站:东方财富网:http://quote.eastmoney.com/center/gridlist.html#hs_a_board
-
技巧:在谷歌浏览器中进入F12调试模式进行抓包,查找股票列表加载使用的url,并分析api返回的值,并根据所要求的参数可适当更改api的请求参数。根据URL可观察请求的参数f1、f2可获取不同的数值,根据情况可删减请求的参数。
-
输出信息:
序号 | 股票代码 | 股票名称 | 最新报价 | 涨跌幅 | 涨跌额 | 成交量 | 成交额 | 振幅 | 最高 | 最低 | 今开 | 昨收 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 688093 | N世华 | 28.47 | 62.22% | 10.92 | 26.13万 | 7.6亿 | 22.34 | 32.0 | 28.08 | 30.2 | 17.55 |
...... |
1.思路及核心代码
1.1网页分析
首先进入网站,进入F12调试模式进行抓包。通过分析得到了我们想要的js,这个js的请求URL中的fields
中的参数f1,f2,……中包含我们需要的字段,如下图所示。
通过仔细比对,可以得出我们需要爬取的字段参数如下:
"股票代码": 'f12',
"股票名称": 'f14',
"最新报价": 'f2',
"涨跌幅": 'f3',
"涨跌额": 'f4',
"成交量": 'f5',
"成交额": 'f6',
"振幅": 'f7',
"最高": 'f15',
"最低": 'f16',
"今开": 'f17',
"昨收": 'f18'
1.2设计股票数据库类
StockDB类与上一题的大同小异,但是,由于本次需要爬取的字段众多,所以一个一个手打并不是明智的选择。
一些小技巧:
-
在插入数据的时候,可以用
executemany()
插入多条数据元组的列表。 -
在输出数据库的数据信息的时候,format()函数的参数可以支持可迭代的数据类型,只需要在
list
或tuple
之前加上*号。例如
ls=[1,2,3,4] print("{}{}{}{}".format(*ls))
输出:
1234
效果与
print("{}{}{}{}".format(ls[0],ls[1],ls[2],ls[3]))
是一模一样的,本作业中使用这个可以大大减少代码量。
# 插入数据的方法
def insert(self, stock_List):
try:
self.cursor.executemany("insert into stock (股票代码 , 股票名称 ,最新报价 , 涨跌幅 ,涨跌额 , 成交量 ,成交额 ,振幅 , 最高 , 最低 , 今开 , 昨收 ) values (?,?,?,?,?,?,?,?,?,?,?,?)", stock_List)
except Exception as err:
print(err)
def show(self):
self.cursor.execute("select * from stock")
rows = self.cursor.fetchall()
print("{:8}\t{:16}\t{:8}\t{:8}\t{:8}\t{:8}"
"\t{:16}\t{:8}\t{:8}\t{:8}\t{:8}\t{:8}".format("股票代码", "股票名称", "最新报价", "涨跌幅", "涨跌额",
"成交量", "成交额", "振幅", "最高", "最低", "今开", "昨收",
chr(12288)))
for row in rows:
print("{:8}\t{:16}\t{:8}\t{:8}\t{:8}\t{:8}"
"\t{:16}\t{:8}\t{:8}\t{:8}\t{:8}\t{:8}".format(*row, chr(12288)))
1.3 爬取网页
由于爬取的数据是json格式的字符串,所以最好是使用json
包中的json.loads()
来进行解析,可以省时省力。
然后将爬取的数据存入stockList2
中,这个list的每个元素是一个元组,然后可以用executemany()
配合插入多条元组组成的列表
req = requests.get(url)
req.raise_for_status()
req.encoding = req.apparent_encoding
data = req.text
data = re.search(r'\[.*]', data).group() # 截取出json格式的数据
stockList = re.findall(r'{.*?}', data) # 得到的是json格式的列表
stockList = [json.loads(x) for x in stockList] # 得到python的dict格式数据
att = {"股票代码": 'f12', "股票名称": 'f14', "最新报价": 'f2', "涨跌幅": 'f3', "涨跌额": 'f4', "成交量": 'f5', "成交额": 'f6',
"振幅": 'f7', "最高": 'f15', "最低": 'f16', "今开": 'f17', "昨收": 'f18'} # 要爬取的字段及其对应的url参数
stockList2 = [] # 用来存储整个数据表
for stock in stockList:
ls = []
for i in att:
ls.append(stock[att[i]])
ls = tuple(ls)
stockList2.append(ls)
db = StockDB()
db.openDB()
db.insert(stockList2)
print("成功导入数据库")
db.show()
db.closeDB()
1.4结果
控制台输出
数据库可视化
1.5 码云链接: 作业2
2.心得体会
-
本次需要爬取的字段众多,所以我通过探索学会了在format()函数中通过*加上可迭代的数据类型进行传递参数,节省了重复枯燥的代码。
-
用json包可以很方便的解析json格式的字符串
作业③:
- 要求: 爬取中国大学2021主榜 https://www.shanghairanking.cn/rankings/bcur/2021
所有院校信息,并存储在数据库中,同时将浏览器F12调试分析的过程录制Gif加入至博客中。 - 技巧: 分析该网站的发包情况,分析获取数据的api
- 输出信息:
排名 | 学校 | 总分 |
---|---|---|
1 | 清华大学 | 969.2 |
1.思路及核心代码
1.1 分析网页
首先进入网站,进入F12调试模式进行抓包。通过分析得到了我们想要的js,如下图所示。
通过分析js的内容可以发现,使用第二题的json解析并不方便,所以使用正则表达式进行匹配。
1.2 编写数据库类:
与作业1大同小异,需要注意的是,本次爬取的内容中,score字段可能为不是数值,所以需要在存入和取出数据库的时候进行适当的转换。在数据库类的show()
方法中,数据库中的值为NULL
,则对应在python则为None
,在输出时,将其转换为’-‘。
同时,在format()
中也使用和作业2一样的方法传递参数。
def show(self):
self.cursor.execute("select * from University")
rows = self.cursor.fetchall()
print("{: ^3}{: ^10}{: ^5}".format('排名', '大学', '总分'))
for row in rows:
# 判断score字段是否为None,如果是,则设置为‘-’
if row[2] is None:
row = list(row)
row[2] = '-'
print("{: ^3}{: ^13}{: ^5}".format(*row))
1.3 爬取和提取目标文本
req = requests.get(url)
req.raise_for_status()
req.encoding = req.apparent_encoding
data = req.text
usName = re.findall(r'univNameCn:".*?"', data) # 匹配所有的大学名称
usName = [x[x.find('"') + 1:-1] for x in usName] # 提取出大学名称
score = re.findall(r'score:.*?,', data) # 匹配所有的大学分数
score = [x[x.find(":") + 1:-1] for x in score] # 提取出大学分数
1.4 将异常的score值转换为None
temp = []
for x in score:
# 判断分数是否是数值,如果不是,则设置为None,None插入数据库会自动转换为Null
try:
temp.append(eval(x))
except Exception as e:
temp.append(None)
score = temp
1.5 存入数据库
University = UniversityDB()
University.openDB()
for i in range(len(usName)):
University.insert(i+1, usName[i], temp[i])
University.show()
University.closeDB()
1.6 结果
程序输出
数据库可视化
1.7 代码链接
2.心得体会
在爬取到异常值后,要对异常值进行处理再存入数据库。数据库中的值为NULL
,则对应在python则为None