社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

JB的Python之旅-爬虫篇-新浪微博内容爬取

jb • 5 年前 • 454 次点击  
阅读 15

JB的Python之旅-爬虫篇-新浪微博内容爬取

前言

记得半个月之前的一晚,媳妇跟jb说,你看,苍老师发了条微博,内容为69,后来微博官方关闭了该条微博的评论功能~

虽然不知道69是什么意义,但是看评论,总感觉是要开车了~

听说,苍老师是90后启蒙的一代,虽然没有经历过,但依然心存敬佩,于是乎,就像把苍老师的微博内容都爬出来,看看老师都发了些什么~

PC找内容

打开链接:
https://weibo.com/u/1739928273?refer_flag=1001030101_&is_all=1#_rnd1530254292380
打开后就是苍老师的微博链接,可以看到,下面就是苍老师发布的微博拉,而内容就是我们想要的东西~

像微博这种大厂,想都不用想就知道解析html获取数据这条路是行不通的,那我们F12 刷新下网页,看看请求?

嗯,好多js跟css,那我们一个一个看,看看能不能找到有用的信息;

(5分钟过去了)尼玛,怎么一条微博动态都没看到,怎么获取?

尝试多次,依然找不到解决方案,欲想放弃,此时,三三同学说,用wap版,微博有接口获取!!!

转战手机版

就这样,转战手机版,手机版苍老师链接如下:https://m.weibo.cn/u/1739928273;
老规矩,F12刷新网页,然后把请求一条一条过,结果发现一个玩意:

比起PC版,手机版终于看到有点类似数据的东西了,那我们打开第一条看看~

这里面的text不就是跟苍老师的第一条微博是一样的吗?get~这就是我们需要的东西啦~

那我们点击headers,把request url拿出来分析下:

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273

解析这这个url,这url带有3个参数:

type=uid
value=1739928273
containerid=1076031739928273

这3个参数,唯一能确定的就是value,为什么这么说?回头看看苍老师手机版的链接:https://m.weibo.cn/u/1739928273,由此得知,1739928273就是苍老师微博的ID,不信, 你随便改下试试,可能会跳到其他老师那呢~

这不,简单把最后2位73改成12,就变成另一位美女了~

貌似跑题了,咳咳,刚刚说到哪~

嗯,知道这几个参数,没啥特别的,那我们试试滑动下屏幕,往下拉,拉取更多的数据,最后使用上面的方式,获取url:

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273&page=3

与上面的url不同的是,这里多了个参数page=3,不用想都知道,这是代表第三页的意思了~
结果分析,如果是第二页,page=2,第一页的话,是不携带page参数,但是尝试把page=0跟page=1两个情况,返回的数据跟不携带page参数是一直的,所以后面就把首页当做是page=1的情况处理~

ok,现在知道了数据在哪里,翻页怎么弄,那我们就看看请求头把~

咦,Provisional headers are shown这是什么,其他请求内容没看到?
网上找了说,是这么解释:请求的资源可能会被(扩展/或其他什么机制)屏蔽掉。
更详细的信息的话,请看:https://segmentfault.com/q/1010000000364871/a-1020000000429614

这个东西对于我们有影响吗?暂时看是没有的,从上图就能看到请求的内容跟携带的参数~

那我们打开看看,下面这条链接是什么内容?

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273

嗯,打开之后,是这样的,wtf??这是什么??

此时,赶紧看看苍老师的内容:

内容的对应字段是text,那我们copy去到刚刚那个页面搜索下:

都变成了\u6211\u81ea\u5df这种玩意了~

这个问题,之前在写urllib的时候也说明过:

URL只允许部分ASCLL(数字字母和部分符号),其他的字符(包括汉字)是不符合URL的标准,
所以URL需要对这些字符进行URL编码,URL编码的方式是把需要编码的字符转化为 %xx 的形式。
通常 URL 编码是基于 UTF-8 的,函数说明也提及到给予UTF-8进行encode~

Ok,那就说,提取text的内容就好啦~那我们先写个请求吧

# -*- coding:utf-8 -*-
import requests

url = "https://m.weibo.cn/api/container/getIndex"
#请求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1739928273",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
    "Accept":'application/json, text/plain, */*',
    "X-Requested-With":"XMLHttpRequest",
}
#请求头

params = {
          "type": "uid",
          "value": "1739928273",
          "containerid": "1076031739928273",
          "page": "1"}
#请求携带的参数


res = requests.get(url,headers=headers,params=params).content
print(res)

执行后,得到的结果是这样的:

核对了下,跟网页访问是一样的~

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273&page=1

接下来可以干嘛?就可以写正则来匹配啦,先找data,然后找cards,然后再获取每个cards下面的text;

如果真的如上面说的,马上写噼里啪啦写正则,就有点冲动了,看下返回结果的格式,感觉是不是像json?

"ok":1,"data":{"cardlistInfo":{"containerid":"1076031739928273","v_p":

没错,这就是json,那我们就可以换一种方式处理~

res = requests.get(url,headers=headers,params=params)
cards  = res.json().get("data").get("cards")
#获取carads下的所有项

获取的就是下面这个截图的所有cards项:

那我们看看cards里面的内容,这是第一个:

这是第二个:

嗯,有发现不同了吗?对的,就是有多了个关注XXX的一项,但是这一项,但是这一项不是我们要的,那怎么搞?
逐个逐个分析,会发现,正常的数据都有这么一项:

那我们就拿这项做判断吧,先判断这项是否等于9,然后再获取mblog,再获取text里面的内容,于是乎就有了下面的代码:

for card in cards:
    if card.get("card_type") == 9:
        text = card.get("mblog").get("text")
        print(text)

输出的结果如下:

嗯,内容都获取到了,但是有奇怪的东西进来了~

回头看了下,并不是代码的错,而且因为发布的内容有带图片或者表情~

这种情况过滤掉就好了~只需要文字~

pattern = re.compile(r"(.*?)<span.*>(.*?)")
text = re.sub(pattern,"",text)

从上面可以看到问题例子如下,那我们只需要把里面的内容都干掉就好了~

啊啊啊啊啊啊啊啊啊
<span class = "url-icon"><img alt = [允悲] src = "http://img2.jintiankansha.me/get3?src=http://user-gold-cdn.xitu.io/2018/6/29/1644b1bf47628785?w=32&h=32&f=png&s=2591" style = "width:1em; height:1em;"/></span> 

结果如下:

这里有个不解之谜,就是会看到,会有换行,原因是这样的:

这尼玛,居然有个换行符??那我们把获取到的text打印以下~

我去,这个换行符已经换行了,没办法匹配啊~本来还把想把\n换行符先干掉了,这个就是这个换行符的来源,怎么办?

之前在介绍urllib的时候提及有,urllib有一个quote的方法,函数说明提及到给予UTF-8进行encode;

import urllib
kw = urllib.request.quote("很紧张啊啊啊啊↵<s")
print(kw)

输出的内容长这样的:

%E5%BE%88%E7%B4%A7%E5%BC%A0%E5%95%8A%E5%95%8A%E5%95%8A%E5%95%8A%E2%86%B5%3Cs

那我们把中文都去掉,只留↵看看?

%E2%86%B5

得到的结果就是这样的,OK,那假如我们把上面这串结果匹配成空格,是不是就能解决问题?

但实际尝试了下,是不行的,那我们就把数据打出来:

啊啊啊啊啊啊啊啊啊
<s

最后会发现%0A才是那个回车符

%8A%0A%3Cspan

去掉之后,会发现字符的确不见了,而且的的确不会换行了,问题解决;

    kw = urllib.request.quote(text)
    old_kw = re.sub("%0A","",kw)
    new_kw = urllib.request.unquote(old_kw)

回到正题,按照上面的代码爬下来的东西,好像没啥问题,但认真一看,咦~
这里谁说老师下垂了?

对应的微博内容:

这里,很明显是之前的正则有问题,那我们重新折腾下正则,此处过程就不说了,很痛苦。。最后改成这样:

pattern = re.compile(r"<.*?>")

意思就是把<>符号内的内容都去掉,得出的结果:

//@李太白的表哥:老师…你好像下垂了……
查看图片

这里可以看到,查看图片也是多余的,那我们也去掉,包括有一些是转发微博的,也都干掉吧,就成这样了~

pattern = re.compile(r"<.*?>|转发微博|查看图片")

执行下,结果是这样了,看上去很好:

//@李太白的表哥:老师…你好像下垂了……

ok,这个是一个页面的内容抓取,整体代码如下:

# -*- coding:utf-8 -*-
import requests
import re
import urllib


url = "https://m.weibo.cn/api/container/getIndex"
#请求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1739928273",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
    "Accept":'application/json, text/plain, */*',
    "X-Requested-With":"XMLHttpRequest",
}
#请求头

params = {
          "type": "uid",
          "value": "1739928273",
          "containerid": "1076031739928273",
          "page": "1"}
#请求携带的参数

res = requests.get(url,headers=headers,params=params)
cards  = res.json().get("data").get("cards")
#获取carads下的所有项

for card in cards:
    if card.get("card_type") == 9:
        text = card.get("mblog").get("text")
        # kw = urllib.request.quote(text)
        # old_kw = re.sub("%0A","",kw)
        # new_kw = urllib.request.unquote(old_kw)
        # %0A  这串数字对应的就是这个回车字符
        pattern = re.compile(r"<.*?>|转发微博|查看图片")
        #这里就是把<>符号内的都匹配出来
        text = re.sub(pattern,"",text)
        print(text)

其他优化

既然一页搞定了,那我们要爬多页,怎么破?这个很简单啦,直接改page参数就行了
另外还遇到一个问题:

假如我们设定爬取1000页,但是实际上,用户可能只有100页的数据,那脚本还是会一直爬取的~
处理方案,加多一个参数,统计上一次的长度,如果相同,则认为没有新数据,暂停脚本处理

数据这多了,还发现这种东西~当然,也是正则兼容下就行了~

最终代码

结果上面的处理,缝缝补补,最终代码如下:




    
# -*- coding:utf-8 -*-
import requests
import re
import urllib
import codecs


url = "https://m.weibo.cn/api/container/getIndex"
#请求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1761379670",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
}
#请求头

params = {
          "type": "uid",
          "value": "{uid}",
          "containerid": "{containerid}",
          "page":"{page}"}
#请求携带的参数


def get_Data(uid="3303658163", containerid="1005053303658163"):
    total = 3000  #打算爬取的页数,比如100页
    content = []  #存放获取到的微博正文内容
    page =1  #页码,从第一页开始算
    last_length = 0 #上一个的内容长度,用于对比上一次的总体内容长度跟这次是否一致,如果一致,则认为没有新数据,停止脚本处理
    for i in range(total):
        params["page"] = str(page)
        params['uid'] = uid
        params['containerid'] = str(containerid)
        res = requests.get(url, headers=headers, params=params)
        print(res.json().get("data"))
        cards = res.json().get("data").get("cards")
        # 获取carads下的所有项


        for card in cards:
            if card.get("card_type") == 9:
                text = card.get("mblog").get("text")
                kw = urllib.request.quote(text)
                old_kw = re.sub("%0A","",kw)
                new_text = urllib.request.unquote(old_kw)
                # %0A  这串数字对应的就是这个回车字符
                pattern = re.compile(r"<.*?>|转发微博|查看图片|查看动图|&gt;")
                #这里就是把<>符号内的都匹配出来,正则规则
                text = re.sub(pattern,"",new_text)
                content.append(text)
        page +=1
        if (len(content) == last_length):
            print("已经获取不到更多内容,脚本暂停处理")
            break
        else:
            last_length = len(content)
            print("抓取第{page}页,目前总共抓取了 {count} 条微博".format(page=page, count=len(content)))
            with codecs.open('jb.txt', 'w', encoding='utf-8') as f:
                f.write("\n".join(content))


if __name__ == '__main__':
    get_Data("1761379670", "1005051761379670")

功能介绍的话,一路看下来就很明朗了,一句话就是,解析json而已;

可能有同学问,上面的代码如何使用?直接copy出来执行即可,如果想爬某人的信息,比如吉泽明步:

https://m.weibo.cn/u/2360092592?uid=2360092592&luicode=10000011&lfid=100103
type%3D1%26q%3D%E5%90%89%E6%B3%BD%E6%98%8E%E6%AD%A5

打开她的手机版微博主页

然后把浏览器的F12,重新刷新下网页,搜索get关键词,从而获得value跟containerid,直接填写到get_Data方法里面即可~

最后的输出结果如下:

说明

该脚本可能依赖于网页结果,一旦网页结构发生变化,该脚本即不适用,请了解~
尝试过20个左右的用户,均可数据,如遇到问题,请留言告知,谢谢~

感谢

本文感谢三三同学的极力支持,否则如研究PC版,估计就凉了~

小结

本文主要解析怎么爬取手机版的微博内容,主要原理是解析json,遇到有趣的问题有2个,第一是正则,想获取什么,把不需要的处理掉就好了,不然什么都()去做,太麻烦了~第二,微博的换行符,一开始还想着\n匹配处理,结果发现不行,后来换个角度,弄成编码的格式就发现问题了;

好了,本文到此,谢谢大家~


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/DXX8M21DK9
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/19632
 
454 次点击