Python 爬虫实战:某蜂窝网游记爬虫

大家好,我是程序员晓晓。

今天给大家分享 某蜂窝网游记 的爬虫及爬取思路。

示例网址:aHR0cHMlM0EvL3d3dy5tYWZlbmd3by5jbi90cmF2ZWwtc2NlbmljLXNwb3QvbWFmZW5nd28vMTMwMzMuaHRtbA==

我们以 山西 为例。打开网址,页面向下翻,便可以看到 山西游记 区域。

Part1观察网页

写过爬虫的大家都知道,写爬虫第一步,需要观察目标网站。

具体观察哪些东西呢?主要有两个方面,目标数据页面结构

1.1观察目标数据

这一步主要是为了确定,我们可以获取到哪些 数据项,以及大概的 数据量

如图可以看到,从游记列表中,我们可以得到游记的 标题题图摘要作者浏览量评论量 等数据。此外,通过分析源代码,我们还可以得到游记的 id链接

观察游记列表的翻页区,我们可以得知游记的 总页数总条数 ,如山西游记有 3355 页,33544 条。

经过实际测试,无论是通过爬虫抓取,还是手动翻页,游记数据最多只能访问 100 页,1000条数据。

1.2观察页面结构

观察页面结构,主要是为了确定两件事情:

  1. 翻页方式(瀑布流还是按钮翻页)

  2. 数据加载方式(动态还是静态)

因为不同的翻页方式和数据加载方式,会影响后续我们抓取数据、解析数据的方式。

简单观察网页以后不难发现,

  1. 列表底部有 翻页按钮 ,点击后可以跳转至对应页码。

  2. 翻页时,仅游记列表区域数据发生了更新(网页整体没有刷新),很明显是使用了 Ajax 动态加载 数据。

Part2抓包测试

2.1抓包

知道网站是动态加载数据以后,我们直接开始抓包。

按F12键打开浏览器的 开发者工具,切换到 Network ,过滤器选择 Fetch/XHR

点击 翻页 按钮,抓到一条请求包,点击这条 ajax.php?act=get_travellist 请求包以后,可以看到它的详细情况。

Headers 中可以看到它的请求地址 Request URL 和请求方法 Request Method

Payload 中可以看到它的请求参数。

PreviewResponse 中可以看到它的响应内容。

为了确认抓到的包对不对,我们需要检查请求的响应内容,看其中是否包含了我们需要的数据。

抓到正确的请求包以后,我们接下来要做的就是,用代码模拟发起请求。

2.2反爬测试

我们简单写一段测试代码,尝试获取数据,顺便检查一下网站的反爬机制。

# 请求地址
url = "https://www.mafengwo.cn/gonglve/ajax.php?act=get_travellist"
# 请求头
headers = {
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Encoding": "gzip, deflate, br, zstd",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Content-Length": "105",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "your own cookie",
    "Origin": "https://www.mafengwo.cn",
    "Pragma": "no-cache",
    "Referer": "https://www.mafengwo.cn/travel-scenic-spot/mafengwo/13033.html",
    "Sec-Ch-Ua": "\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"",
    "Sec-Ch-Ua-Mobile": "?0",
    "Sec-Ch-Ua-Platform": "\"Windows\"",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "same-origin",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}
# 请求参数
payload = {
    "mddid": "13033",
    "pageid": "mdd_index",
    "sort": "1",
    "cost": "0",
    "days": "0",
    "month": "0",
    "tagid": "0",
    "page": "2",
    "_ts": "1701158589677",
    "_sn": "d65ae26004"
}

从开发者工具中,我们找到 请求地址请求头请求参数 等,格式化后粘贴到代码中。

# 导入包
import requests
# 发起 post 请求
r = requests.post(url, headers=headers, data=payload)
# 打印数据
print(r.json())

运行代码,发起网络请求,发现可以成功获取数据。

2.3简化参数

此时 headerspayload 里的参数还是比较多的,但是这些参数并不是每项都必要。所以我们可以进行简化,将不必要的参数删掉。

简化的方法比较简单粗暴,就是逐个删除参数,并运行代码

如果删除某个参数后,运行结果没有变化,说明该参数不是必要的,可以删掉;如果删掉某个参数后,运行结果为空或者报错了,说明该参数是必须的,不能删除。

最终简化后的代码如下:

# 请求地址
url = "https://www.mafengwo.cn/gonglve/ajax.php?act=get_travellist"
# 请求头
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
}
# 请求参数
payload = {
    "mddid": "13033",
    "pageid": "mdd_index",
    "sort": "1",
    "page": "2",
}

# 导入包
import requests
# 发起 post 请求
r = requests.post(url, headers=headers, data=payload)
# 打印数据
print(r.json())

Part3编写代码

搞定了抓包和反爬测试以后,我们就可以开始编写代码了。

3.1网络请求

将前面的网络请求的测试代码封装一下。

# 导入包
import requests

def fetchUrl(cityId, page):
    """
    cityId: 城市 id
    page:   页码
    """
    
    # 请求地址
    url = "https://www.mafengwo.cn/gonglve/ajax.php?act=get_travellist"
    # 请求头
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
    }
    # 请求参数
    payload = {
        "mddid": cityId,
        "pageid": "mdd_index",
        "sort": "1",
        "page": page,
    }

    # 发起 post 请求
    r = requests.post(url, headers=headers, data=payload)
    # 返回数据
    return r.json()

# 调用函数
fetchUrl(13033, 2)

3.2解析数据

数据获取到后,接下来就是解析数据了。

该请求返回的数据,格式如下:

{
    list: "游记列表的 html 内容",
    msg: "succ",
    page: "翻页按钮的 html 内容"
}

我们需要的数据以 html 的格式存放在 list 下。

可以通过以下方式将其解析出来。

jsonObj = fetchUrl(13033, 2)
# 解析游记列表的 html 内容
html = jsonObj["list"]

现在我们得到了游记列表的 html 文本内容,格式如下:

<div class="tn-list">
    <div class="tn-item clearfix">
  <div class="tn-image">题图</div>
  <div class="tn-wrapper">
   <dl>
    <dt>
     <a href="/i/24193768.html" class="title-link">标题</a>
    </dt>
    <dd>
     <a href="/i/24193768.html">摘要</a>
    </dd>
   </dl>
   <div class="tn-extra">
    <span class="tn-user">
     <a href="/u/92844923.html">作者</a>
    </span>
    <span class="tn-nums">浏览量/评论数</span>
   </div>
  </div>
    </div>
    <div class="tn-item clearfix"></div>
    <div class="tn-item clearfix"></div>
    ......
</div>

接下来我们可以使用 BeautifulSoup 库来进行解析。

# 导入库
from bs4 import BeautifulSoup
# 实例化 bs 对象
bsObj = BeautifulSoup(html, "html.parser")
# 获取所有的游记 item
tn_items = bsObj.find_all("div", attrs={"class": "tn-item"})

# 循环解析每一个 item
for item in tn_items:
 # 题图
    headImage = item.find("div", attrs={"class": "tn-image"}).a.img["data-original"]
    # 标题
    titleNode = item.dt.find_all("a")[-1]
    title = titleNode.text
    # 链接
    link = "https://www.mafengwo.cn" + titleNode["href"]
    # 摘要
    desc = item.dd.text
    # 作者
    author = item.find("span", attrs={"class": "tn-user"}).text
    
    nums = item.find("span", attrs={"class": "tn-nums"}).text.strip().split("/")
    # 浏览量
    views = nums[0]
    # 评论数
    comments = nums[1]
    
    # 测试打印
    print("题图:", headImage)
    print("标题:", title)
    print("链接:", link)
    print("作者:", author)
    print("浏览量:", views)
    print("评论数:", comments)
    print("摘要:", desc)
    print("---"*10)

运行结果

最后将代码简单封装一下即可。

def parseHtml(html):
    # 实例化 bs 对象
    bsObj = BeautifulSoup(html, "html.parser")
    # 获取所有的游记 item
    tn_items = bsObj.find_all("div", attrs={"class": "tn-item"})
    
    itemList = []

    # 循环解析每一个 item
    for item in tn_items:
     # 题图
        headImage = item.find("div", attrs={"class": "tn-image"}).a.img["data-original"]
        # 标题
        titleNode = item.dt.find_all("a")[-1]
        title = titleNode.text
        # 链接
        link = "https://www.mafengwo.cn" + titleNode["href"]
        # 摘要
        desc = item.dd.text
        # 作者
        author = item.find("span", attrs={"class": "tn-user"}).text
  # 浏览量
        nums = item.find("span", attrs={"class": "tn-nums"}).text.strip().split("/")
        views = nums[0]
        # 评论数
        comments = nums[1]
        
        itemList.append([title, link, author, views, comments, headImage, desc])

#         # 测试打印
#         print("题图:", headImage)
#         print("标题:", title)
#         print("链接:", link)
#         print("作者:", author)
#         print("浏览量:", views)
#         print("评论数:", comments)
#         print("摘要:", desc)
#         print("---"*10)

    return itemList

3.3存储数据

我们准备用 csv 格式文件来存储数据。

这里使用了第三方库 pandas

# 导入包
import pandas as pd

def saveData(filename, data):
    """
    filename: 文件名
    data: 要存储的数据
    """
    dataframe = pd.DataFrame(data)
    dataframe.to_csv(filename, encoding='utf_8_sig', mode='a', index=False, sep=',', header=False)

3.4主函数

最后,在主函数中写一个 for 循环,启动爬虫即可。

if __name__ == "__main__":
    cityId = 13033
    filename = "山西.csv"
    totalPage = 100
    
    # 保存标题行
    header = [["标题","链接","作者","浏览量","评论数","题图","摘要"]]
    saveData(filename, header)
    
    # 循环爬取数据
    for page in range(1, totalPage + 1):
        # 请求数据
        jsonObj = fetchUrl(cityId, page)
        # 解析数据
        data = parseHtml(jsonObj["list"])
        # 保存数据
        saveData(filename, data)

至此,我们的爬虫主体已经全部完成了。

3.5查改 BUG

实际运行中会发现,虽然我们的爬虫已经完成,可以成功抓取到数据,但是非常容易报错崩溃。

因为前面为了追求效率而写的很多并不严谨的代码,导致程序非常的脆弱。

比如这一句代码,用于解析游记的题图链接。

headImage = item.find("div", attrs={"class": "tn-image"}).a.img["data-original"]

实际执行时,抓取到某些游记时,会发生报错。

究其原因,是因为在游记列表中,游记的题图标签有两种写法。

<!-- 写法 1 -->
<img class="lazy" data-original="https://xxxx.jpg" height="150" width="220"/>
<!-- 写法 2 -->
<img src="https://xxxx.jpg" height="150" width="220"/>

代码中如果直接获取标签的 data-original 属性,则遇上 写法 2 的标签便会报错。

因此我们在获取标签属性前,需要先判断属性是否存在。

# 获取题图,代码优化示例
imageNode = item.find("div", attrs={"class": "tn-image"}).a.img
if imageNode.has_attr("src"):
    headImage = imageNode["src"]
elif imageNode.has_attr("data-original"):
    headImage = imageNode["data-original"]
else:
    headImage = ""

以上作为查改BUG 和优化代码的示例,给大家参考。

在爬取大量数据的时候,还会遇到更多兼容性方面的 bug,需要大家逐一优化。

3.6运行效果

将代码完善后,运行结果如下:

保存好的 csv 文件用 excel 打开,简单整理格式后效果如下:

Part4其它问题

爬取游记需要填写城市的 ID,这一步对于有爬虫基础的同学应该问题不大。

不知道如何获取城市 id 的同学,我这里也提供了一段脚本,可以根据城市名字自动获取城市 ID。

import requests
import re

def getCityCode(city):
    
    url = 'https://www.mafengwo.cn/search/q.php?q=%s' % city

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
    }

    r = requests.get(url, headers=headers)
    # 正则提取城市编码
    code = re.findall("type=10&id=([0-9]+)", r.text)
 # 正则提取城市名称
    title = re.findall('''<div class="info">
                <p class="title">(.*)</p>''', r.text)
    
    if len(code) > 0 and len(title) > 0:
        return (title[0], code[0])
    else:
        return (city, "找不到该城市!")

# 测试代码
a = getCityCode("山西")
print(a)

运行结果:

('山西', '13033')   

经测试,在输入的城市名有错别字,甚至是拼音的情况下,代码也可以顺利获取城市 ID。

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述
若有侵权,请联系删除
  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值