基于BeautifulSoup解析的网页爬虫实现
目标
爬去cnkongqi.com上的气象数据,cnkongqi.com站点上包含全国每个城市的天气,空气质量等数据,这些数据每小时都会进行更新。我的目标是要将该站点的某一时间的气象数据全部抓取下来,并保存到数据库。
本次爬虫编写语言选用python,由于在下对python是小白,所以这个爬虫程序可以算是我的第一个完整的python爬虫。以前使用过java编写一些爬虫程序,爬去够某些小说网站的小说,所以对爬虫的原理有着一定的理解,根据该原理使用python进行实现。
分析
cnkongqi.com上的某一个城市的气象数据地址为:http://www.cnkongqi.com/pc/510100.htm ,每小时数据更新后地址不变。而且每个页面都包含一个城市目录,该站点没有单独的目录页,所以需要预先在本地创建城市数据页面目录。创建好城市目录后,即可进行遍历目录进行数据爬去和存储。如果需要不断的更新本地数据,只需要每小时执行一次便利代码即可。需要完成:
- 目录创建
- 遍历目录获取气象信息你
实现
- 使用urllib来获取目标页面html
- 使用Beautifulsoup解析html
- 使用pymysql进行数据存储
数据库设计
根据站点数据结构特点,设计了4张表:
- 省份目录 t_province
CREATE TABLE t_province
(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(20)
);
- 城市目录 t_city
CREATE TABLE t_city
(
id INT(11) PRIMARY KEY NOT NULL,
name VARCHAR(30),
provinceId INT(11),
infoUrl VARCHAR(50),
updateTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
- 城市气象数据 t_city_data
CREATE TABLE t_city_data
(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
tq VARCHAR(5) COMMENT '天气',
wd FLOAT(5,2) COMMENT '温度 。C',
sd FLOAT(5,2) COMMENT '湿度 %',
fx VARCHAR(20) COMMENT '风向',
zs INT(11) COMMENT '指数',
dj VARCHAR(20) COMMENT '等级',
cityId INT(11) COMMENT '城市编号',
updateTime TIMESTAMP COMMENT '更新时间'
);
- 城市观测点空气质量数据 t_city_point_data
CREATE TABLE t_city_point_data
(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
pointName VARCHAR(20) COMMENT '监测点名称',
pm25 FLOAT(6,2) COMMENT 'pm2.5浓度',
aqi_cn INT(11) COMMENT '中标AQI',
aqi_us INT(11) COMMENT '美标AQI',
cityId INT(11) COMMENT '城市编号',
updateTime TIMESTAMP COMMENT '更新时间'
);
省份及城市目录创建
目录创建代码仅需执行一次即可。
由于python的易读性,就不赘述了,直接上代码:
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pymysql
# 创建数据库连接
conn = pymysql.connect(host='127.0.0.1', user='root', passwd='123456', db='test', charset="utf8")
cur = conn.cursor()
cur.execute("use test")
# 存储省份数据
# code - 省份编号
# name - 省份中文名
def storeProvince(code, name):
cur.execute("INSERT INTO t_province VALUES (%s,%s)", (code, name))
cur.connection.commit()
# 存储城市目录
# code - 城市编号
# name - 城市中文名
# pcode - 所属省份编号
# url - 城市气象数据地址
def storeCity(code, name, pcode, url):
cur.execute("INSERT INTO t_city (id, `name`, provinceId, infoUrl) VALUES (%s,%s,%s,%s)", (code, name, pcode, url))
cur.connection.commit()
# 任意城市页面html获取
html = urlopen("http://www.cnkongqi.com/pc/510100.htm")
# 解析该页面的html
# 注:不添加"html.parse" 会报错,但是参考代码上却没有该参数,对此比较纳闷,求高手指教
bsObj = BeautifulSoup(html.read(), "html.parser")
# 获取该页面的目录信息,并存储
for link in bsObj.find("div", {"id": "div2"}).findAll("a"):
# 获取省份信息
code = link.attrs['id'][4:]
# 存储
storeProvince(link.attrs['id'][4:], link.get_text())
# 获取该省份的城市信息
for alink in bsObj.find("div", {"id": "city_div_" + code}).findAll("a"):
ccode = alink.attrs['id'][5:]
cname = alink.get_text()
curl = "http://www.cnkongqi.com/pc/" + alink.attrs['href']
# 存储
storeCity(ccode, cname, code, curl)
# 关闭资源
cur.close()
conn.close()
遍历目录进行气象数据抓取
数据抓取代码可以重复使用,该站点并未对采集频率进行限制。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pymysql
from pymysql import DataError
# 创建数据库连接
conn = pymysql.connect(host='127.0.0.1',user='root',passwd='123456',db='test',charset = "utf8")
cur = conn.cursor()
cur.execute("use test")
updatecur = conn.cursor()
updatecur.execute("use test")
# 存储城市气象数据
# data - 一条城市数据元组
def saveCityData(data):
try:
updatecur.execute("INSERT INTO t_city_data (tq,wd,sd,fx,zs,dj,cityId,updateTime) VALUES (%s,%s,%s,%s,%s,%s,%s,now())", data)
updatecur.connection.commit()
except DataError:
print(data)
# 存储观测点数据
# data - 一条观测点数据元组
def saveCityPointData(data):
try:
updatecur.execute("INSERT INTO t_city_point_data (pointName, pm25, aqi_cn, aqi_us, cityId,updateTime) VALUES (%s,%s,%s,%s,%s,now())", data)
updatecur.connection.commit()
except DataError:
print(data)
# 更新一个城市的更新时间
def updateCity(code):
updatecur.execute("UPDATE t_city SET updateTime=now() WHERE id=%s", (code))
updatecur.connection.commit()
# 获取html中某个节点的数据,并设置默认值
def getData(source,index, default):
try:
return source[index].get_text()
except IndexError:
return default
# 空值替换
def replaceEmpty(data):
if(data == ''):
return '0'
else:
return data
# 城市目录获取
cur.execute("SELECT * FROM t_city")
# 遍历目录获取城市气象信息
for var in cur.fetchall():
cityCode = var[0]
url = var[3]
# 城市气象信息页面html获取
html = urlopen(url)
# html 解析
bsObj = BeautifulSoup(html.read(), "html.parser")
# 城市气象信息解析
left_left = bsObj.find("div",{"class":"s_left-left"}).findAll("li")
left_right = bsObj.find("div",{"class":"s_right-right"}).findAll("span")
tq = getData(left_left,0,'')
wd = getData(left_left,1,'温度-273.15。C')
sd = getData(left_left,2,'湿度0%')
fx = getData(left_left,3,'-')
zs = getData(left_right,0,'0')
dj = getData(left_right,1,'-')
# 封装元组
cityData = (tq[2:],replaceEmpty(wd[2:len(wd)-2]),replaceEmpty(sd[2:len(sd)-1]),fx,zs.replace('-','0'),dj,cityCode)
# 存储
saveCityData(cityData)
# 获取观测点列表
pointTableTr = bsObj.findAll("table",{"class":"tbaqi"})[1].findAll("tr")
# 遍历观测点
for tr in pointTableTr:
# 空气质量数据解析,并封装元组
td = tr.findAll("td")
pointData = (td[0].get_text(),td[1].get_text().replace('-','0'),td[2].get_text().replace('-','0'),td[3].get_text().replace('-','0'),cityCode)
# 存储
saveCityPointData(pointData)
# 更新该城市,设置更新时间为当前时间
updateCity(cityCode)
# 资源关闭
updatecur.close()
cur.close()
conn.close()