赞
踩
来北京有些日子了,但是每个周末都窝在六环外的村里躺着。想想不能再这么浪费时间了,得出去玩!但是去哪玩呢?于是乎想着,先把北京的景点以及位置都保存起来,然后在分析分析做个出行计划。从哪里获取景点信息呢?也正是最近世界杯马蜂窝的洗脑广告,想着就到马蜂窝上去爬取吧。
本次爬虫使用python开发。自己并不是py工程师,但是还是很喜欢python这门语言的,毕竟人生苦短。所以,一直都想着把python作为一个工具来使用。因此我的目的是达到爬取北京的景点信息,实现了就好。毕竟自己不是专业的,在一些python编程规范以及细节方面,肯定是写得不怎么好,请各位指正。
这里我们用到了请求网络的urllib、mysql连接库pymysql、爬虫利器BeautifulSoup库
首先,我们需要分析一下马蜂窝的展示景点页面。页面链接地址:http://www.mafengwo.cn/jd/10065/gonglve.html。
我们可以看到这个页面主要介绍了必游景点TOP5、热门景点以及北京全部景点。我们需要从最下方的北京全部景点获取到我们需要的信息。
点击了一下翻页,我们可以发现,这些景点列表是通过post调用这个地址:http://www.mafengwo.cn/ajax/router.php 来获取分页信息。参数如下:{‘sAct’: ‘KMdd_StructWebAjax|GetPoisByTag’, ‘iMddid’: 10065, ‘iTagId’: 0, ‘iPage’: page}。可以猜出,iMddid应该是城市id,iPage是分页页码。返回的也是一个json结构的信息,其中包括了列表的html源码。
这样就比较简单了,只要访问这个地址,解析json以及html就可以获取到每个景点的展示地址嘞。参考代码:
post_url = "http://www.mafengwo.cn/ajax/router.php"
page = 1
# 北京景点一共231页
while page <= 231:
param = {'sAct': 'KMdd_StructWebAjax|GetPoisByTag', 'iMddid': 10065, 'iTagId': 0, 'iPage': page}
# 使用urllib
param = urllib.parse.urlencode(param)
param = param.encode('utf-8')
new_url = urllib.request.Request(post_url, param)
response = urllib.request.urlopen(new_url)
# 返回的是一个json格式的字符串,将字符串转为dict对象
data_json = json.loads(response.read().decode("utf8"))
# 获取返回信息中的html格式的li列表
li_list = data_json.get("data").get("list")
# 转为BeautifulSoup对象
soup = BeautifulSoup(li_list, 'html.parser')
beijing_pois = soup.find_all({"li"})
我们在上一步拿到了某个景点的地址,这里就以故宫为例吧http://www.mafengwo.cn/poi/3474.html。访问故宫的景点介绍主页。分析一下页面结构。我们需要的信息有:介绍、用时参考、交通、门票、开放时间、景点经纬度。分析一下dom结构,如图:
我们可以看出,景点的介绍是在class为summary的div里,用时参考是在class为item-time的div里。交通、门票、开放时间都是在标签dl中,每个dl下面的dt都是信息类型,dd是信息的内容。所以,我们抓取页面时,根据class就可以获取详细信息和用时参考,遍历dl判断dt是否为我们需要的信息类型,就可以获取交通、门票、开放时间等信息了。因为并不是所有的景点都有这些信息,所以在这里需要做额外的判断。参考代码:
def get_dl_info(infos, info_name):
for info in infos:
if None != info.dt:
info_title = info.dt.text
if info_name in info_title:
return info.dd.text
return ''
html_context = urlrequest.urlopen(poi_url).read()
soup = BeautifulSoup(html_context, 'html.parser')
# print(soup.prettify())
# 景点简介
poi_summary = ''
if None != soup.find(class_="summary"):
poi_summary = soup.find(class_="summary").text.replace(" ", "")
# 景点游览耗时
poi_time = ''
if None != soup.find(class_="item-time"):
poi_time = soup.find(class_="item-time").text
# 景点信息
infos = soup.findAll({'dl'})
# 交通
poi_traffic = get_dl_info(infos, '交通')
# 门票
poi_ticket = get_dl_info(infos, '门票')
# 开放时间
poi_open_time = get_dl_info(infos, '开放时间')
但是景点的经纬度我并没有在页面上找到。在周边景点那里研究了一番,发现,很神奇,当前景点的经纬度是和查询周边景点时一起返回的。访问地址为:http://pagelet.mafengwo.cn/poi/pagelet/poiLocationApi ,传入景点的poiid即可,返回信息格式也是一个json数据,可以从中获取到周边景点的信息和当前景点的经纬度。格式如下:
参考代码:
get_poi_coordinate_url = 'http://pagelet.mafengwo.cn/poi/pagelet/poiLocationApi?params=%7B"poi_id":"{}"%7D'
class PoiCoordinate:
def __init__(self, poi_lng, poi_lat):
self.poi_lng = poi_lng
self.poi_lat = poi_lat
def get_poi_coordinate(poi_id):
# 调用马蜂窝获取周边景点接口获取poi经纬度
poi_coordinate_context = urllib.request.urlopen(get_poi_coordinate_url.format(poi_id)).read()
poi_coordinate_info = BeautifulSoup(poi_coordinate_context, 'html.parser')
poi_coordinate_json = json.loads(poi_coordinate_info.text)
poi_json = poi_coordinate_json.get("data").get("controller_data").get("poi")
coordinate = PoiCoordinate(poi_json.get("lng"), poi_json.get("lat"))
return coordinate
这样,我们就拿到了我们需要的信息,我们需要将这些信息保存到数据库里的。
我创建了三个表:
CREATE TABLE poi_url
(
id INT AUTO_INCREMENT
PRIMARY KEY,
poi_id INT NOT NULL
COMMENT '景点id',
poi_name VARCHAR(24) NULL
COMMENT '景点名称',
poi_url VARCHAR(255) NULL
COMMENT '景点主页url'
);
CREATE TABLE poi_info
(
id INT AUTO_INCREMENT
PRIMARY KEY,
poi_id VARCHAR(64) NULL,
poi_name VARCHAR(64) NULL,
poi_summary TEXT NULL
COMMENT '景点介绍',
poi_time VARCHAR(100) NULL
COMMENT '景点游览时间',
poi_traffic TEXT NULL
COMMENT '景点交通',
poi_ticket VARCHAR(1024) NULL
COMMENT '景点门票',
poi_open_time VARCHAR(1024) NULL
COMMENT '景点开放时间'
);
CREATE TABLE poi_error
(
id INT AUTO_INCREMENT
PRIMARY KEY,
poi_id INT NOT NULL
COMMENT '景点id',
poi_name VARCHAR(24) NULL
COMMENT '景点名称',
poi_url VARCHAR(255) NULL
COMMENT '景点主页url',
error TEXT NULL
);
我们创建一个数据库连接,然后写一个保存数据的方法,供爬取时调用即可。参考代码:
import pymysql
connection = pymysql.connect(host='127.0.0.1',
user='root',
password='******',
db='wengwengweng',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
autocommit=True)
# 保存poi信息
def insert(insert_connection, insert_sql, param):
with insert_connection.cursor() as cursor:
cursor.execute(insert_sql, param)
ok,这样,我们在保存景点信息以及错误信息时,直接拼装好sql语句和参数,调用insert方法就可以啦。对应部分代码块:
# 保存景点详细信息
insert_poi_info_sql = 'insert into poi_info (poi_id,poi_name, poi_summary, poi_time, poi_traffic, poi_ticket, poi_open_time)' \
' values (%s,%s,%s,%s,%s,%s,%s)'
db_connecter.insert(connection, insert_poi_info_sql,
(poi_id, poi_name, poi_summary, poi_time, poi_traffic, poi_ticket, poi_open_time))
# 保存景点基本信息
insert_poi_url_sql = 'insert into poi_url (poi_id,poi_name,poi_url,poi_lng,poi_lat) values (%s,%s,%s,%s,%s)'
db_connecter.insert(connection, insert_poi_url_sql, (poi_id, poi_name, poi_url, poi_lng, poi_lat))
# 保存错误信息
except Exception:
# 入库错误信息
print(poi_id + poi_name + '写入失败')
insert_poi_error_sql = 'insert into poi_error (poi_id,poi_name,poi_url,error) values (%s,%s,%s,%s)'
db_connecter.insert(connection, insert_poi_error_sql, (poi_id, poi_name, poi_url, traceback.format_exc()))
跑完程序,北京大大小小的景点信息就都保存到数据库中了,有了这些数据,就可以自己随意的去规划出游的路线嘞。
这只是最简单的一次爬虫体验:从最基本的获取页面(或者json格式数据),到分析页面dom节点获取数据,再到数据入库。当然,之后的todo还有很多,比如应对反爬措施(伪装请求head、代理ip、随机延迟访问等),代码优化(抱歉不是专业搞py的肯定写得特别渣)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。