Python爬虫爬取马蜂窝北京景点信息

背景

来北京有些日子了,但是每个周末都窝在六环外的村里躺着。想想不能再这么浪费时间了,得出去玩!但是去哪玩呢?于是乎想着,先把北京的景点以及位置都保存起来,然后在分析分析做个出行计划。从哪里获取景点信息呢?也正是最近世界杯马蜂窝的洗脑广告,想着就到马蜂窝上去爬取吧。

前言

本次爬虫使用python开发。自己并不是py工程师,但是还是很喜欢python这门语言的,毕竟人生苦短。所以,一直都想着把python作为一个工具来使用。因此我的目的是达到爬取北京的景点信息,实现了就好。毕竟自己不是专业的,在一些python编程规范以及细节方面,肯定是写得不怎么好,请各位指正。

涉及到的技术点

html

python库

这里我们用到了请求网络的urllib、mysql连接库pymysql、爬虫利器BeautifulSoup库

爬起来!

获取景点url地址

首先,我们需要分析一下马蜂窝的展示景点页面。页面链接地址: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

数据持久化

这样,我们就拿到了我们需要的信息,我们需要将这些信息保存到数据库里的。
我创建了三个表:

  • poi_url:保存景点的名称、id、访问地址以及经纬度。
  • poi_info:保存景点的详细信息。
  • poi_error:保存爬取异常时的错误信息,以便于进行补偿操作。
    数据库建表语句如下:
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的肯定写得特别渣)。

  • 12
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白码上飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值