爬虫之最新版微博评论爬取方法(非url中拼接page=num方法)

前段时间在整理爬虫知识体系的时候,本着实践出真理的出发点,特意小试牛刀,写了几个不同类型的爬虫。然而在写微博评论的爬虫的时候,意外的发现,微博评论的API竟然变了!据本姑娘记忆中搞一个微博评论的爬虫简直就是飒飒随啦:(1)在谷歌浏览器中以移动端的方式请求访问微博,(2)分析请求列表,找到结构化json的源url,(3)分析url规律,上手撸代码,Bingo!

特意去Google了一下,确认本姑娘的思路没问题,并且获取评论数据的旧API接口url是这个样子滴:https://m.weibo.cn/api/comments/show?id={id}&page={page},此处的id表示要爬的微博的id,page表示第几页的评论数据,同时惊奇的发现,目前这个接口还是可用的(可能还没停止使用,只不过在最新的评论的url中已经找不到了)如:

通过旧接口URL还可以访问到数据

然而作为一个有理想、有追求,求上进的优秀青年肯定不会止步与此,将就着用老接口来爬数据的!

通过以上的步骤分析,我们在谷歌浏览器中打开微博,请求移动端页面,找json数据源url,如下图所示,看到response中得到的json字符串,看到那令人兴奋的红色的ok和data,没错,就是它了。


最新接口URL样式

然后我们提取这个url,给一个特写!

https://m.weibo.cn/comments/hotflow?id=4282494510984677&mid=4282494510984677&max_id=225416516976479&max_id_type=0

可以看到在最新的接口url中,取消了之前的page字段,取而代之的是三个新字段,midmax_idmax_id_type。内心OS:“呵,青铜操作而已啦!”。当然了,结果肯定是要被打脸的,打开此url,得到的结果是这样的:

不得不说有点意思,你已经成功吸引了本姑娘注意力了。

然后呢,我们进行了一波分析,主要包括了以下几个方面:
(1)url地址肯定是没错的,为什么再次打开的时候,得到的结果是{“ok”:0},可以看到正确的返回结果中,ok对应的值应该是1的。两次访问同一url得到结果不一致是什么操作?
(2)新接口相对老接口,取消了page字段,增加了midmax_idmax_id_typemid同微博id一致,因此此字段暂不考虑。那么服务器端是如何通过剩下这两个字段来区分第几页数据?
(3)返回正确结果的请求和返回错误结果的请求两次发起的请求携带的信息会有什么不一致吗?

针对以上几点,经过反复确认尝试,通过fiddler进行多次的抓包分析,得到结果:
(1)确认排除get/post请求方式引起的错误。
(2)对max_id字段数据分析,确认排除通过时间戳来区分不同页数。
(3)确认排除由于两次发起的cookie等信息的不同而被反爬虫。

此时此刻本姑娘已经不开心了,内心已有被调戏的不爽。所以高低一定要拿下,胜利的号角必须要吹响!

先喝杯卡布奇诺冷静一下,再分析接下来应该从哪里入手。从接口url上来看,新老接口的最大不同就在于新接口多了midmax_idmax_id_type三个字段。而且经过了缜密侦查发现response中竟然也有max_idmax_id_type的身影出现。哈!到了这里本姑娘敏锐的感觉好像已经抓到什么了,仿佛已经看到了微博后台小哥哥漏出的半截狐狸尾巴了。

response 中的 json 数据详情

然后本姑娘就猜测,评论数据的第一页的url一定是有所不同滴,让我们来验证一下下,给丫的一个特写!

https://m.weibo.cn/comments/hotflow?id=4282494510984677&mid=4282494510984677&max_id_type=0

看到没有,看到没有!此url链接中没有max_id,哈哈哈哈哈哈哈哈哈,扬天长啸出门去,我辈岂是蓬篙人啊!到这里要先卖个关子,先让我们打开这个链接看看会是什么情况呢?

第一页评论数据url
第一页评论数据

Bingo!果然这是一个与众不同的url,如此的光彩夺目的一个url!第一页的url在地址栏中打开是可以返回数据滴!

好啦,综上三点(1)在response中的json数据中有max_id字段,(2)评论第一页的url中没有max_id字段,(3)评论第一页的url是可以反复多次打开的。本姑娘大胆猜测,把第一页的url的response中的max_id的值拼接到评论第一页的url上得到的结果一定是评论第二页的数据!接下来就是见证奇迹的时刻了:

成功获取到第二页数据

Bingo!完成!
也就是说除了评论数据的第一页的url上不带有max_id字段以外,其他的请求发起时候时,均需携带有上次请求response中返回的max_id数据。例如第二次请求需要携带第一次请求返回的max_id,第三次请求需要携带第二次请求返回的max_id……,依次下去。同时这个顺序是不可逆的,比如当前已经返回了第六页的数据了,此时我们再想访问第三页的数据是不允许的(因为正常操作也是不会出现这种情况滴),而且对同一页的数据进行快速连续访问多次也是不合理的,后台小哥一样会丢给你一个{‘ok’:0}

至此分析部分已经完成,接下来就是愉快滴撸代码啦。首先要爬评论是需要先登陆的,通过登陆获取cookie,之后的请求均携带此cookie。有了cookie了,接下来就是按照上面分析的规则进行评论的爬爬爬爬爬爬爬爬爬,先简单的用Request来走一波,改天上个scrapy版本/scrapy-redis版本。

# -*- coding: utf-8 -*-
import time
import base64
import rsa
import binascii
import requests
import re
from PIL import Image
import random
from urllib.parse import quote_plus
import http.cookiejar as cookielib
import json

agent = 'mozilla/5.0 (windowS NT 10.0; win64; x64) appLewEbkit/537.36 (KHTML, likE gecko) chrome/71.0.3578.98 safari/537.36'
headers = {'User-Agent': agent}

class WeiboLogin(object):
    """
    通过登录 weibo.com 然后跳转到 m.weibo.cn
    """
    #初始化数据
    def __init__(self, user, password, cookie_path):
        super(WeiboLogin, self).__init__()
        self.user = user
        self.password = password
        self.session = requests.Session()
        self.cookie_path = cookie_path
        # LWPCookieJar是python中管理cookie的工具,可以将cookie保存到文件,或者在文件中读取cookie数据到程序
        self.session.cookies = cookielib.LWPCookieJar(filename=self.cookie_path)
        self.index_url = "http://weibo.com/login.php"
        self.session.get(self.index_url, headers=headers, timeout=2)
        self.postdata = dict()

    def get_su(self):
        """
        对 email 地址和手机号码 先 javascript 中 encodeURIComponent
        对应 Python 3 中的是 urllib.parse.quote_plus
        然后在 base64 加密后decode
        """
        username_quote = quote_plus(self.user)
        username_base64 = base64.b64encode(username_quote.encode("utf-8"))
        return username_base64.decode("utf-8")

    # 预登陆获得 servertime, nonce, pubkey, rsakv
    def get_server_data(self, su):
        """与原来的相比,微博的登录从 v1.4.18 升级到了 v1.4.19
        这里使用了 URL 拼接的方式,也可以用 Params 参数传递的方式
        """
        pre_url = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su="
        pre_url = pre_url + su + "&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.19)&_="
        pre_url = pre_url + str(int(time.time() * 1000))
        pre_data_res = self.session.get(pre_url, headers=headers)
        # print("*"*50)
        # print(pre_data_res.text)
        # print("*" * 50)
        sever_data = eval(pre_data_res.content.decode("utf-8").replace("sinaSSOController.preloginCallBack", ''))

        return sever_data

    def get_password(self, servertime, nonce, pubkey):
        """对密码进行 RSA 的加密"""
        rsaPublickey = int(pubkey, 16)
        key = rsa.PublicKey(rsaPublickey, 65537)  # 创建公钥
        message = str(servertime) + '\t' + str(nonce) + '\n' + str(self.password)  # 拼接明文js加密文件中得到
        message = message.encode("utf-8")
        passwd = rsa.encrypt(message, key)  # 加密
        passwd = binascii.b2a_hex(passwd)  # 将加密信息转换为16进制。
        return passwd

    def get_cha(self, pcid):
        """获取验证码,并且用PIL打开,
        1. 如果本机安装了图片查看软件,也可以用 os.subprocess 的打开验证码
        2. 可以改写此函数接入打码平台。
        """
        cha_url = "https://login.sina.com.cn/cgi/pin.php?r="
        cha_url = cha_url + str(int(random.random() * 100000000)) + "&s=0&p="
        cha_url = cha_url + pcid
        cha_page = self.session.get(cha_url, headers=headers)
        with open("cha.jpg", 'wb') as f:
            f.write(cha_page.content)
            f.close()
        try:
            im = Image.open("cha.jpg")
            im.show()
            im.close()
        except Exception as e:
            print(u"请到当前目录下,找到验证码后输入")

    def pre_login(self):
        # su 是加密后的用户名
        su = self.get_su()
        sever_data = self.get_server_data(su)
        servertime = sever_data["servertime"]
        nonce = sever_data['nonce']
        rsakv = sever_data["rsakv"]
        pubkey = sever_data["pubkey"]
        showpin = sever_data["showpin"]  # 这个参数的意义待探索
        password_secret = self.get_password(servertime, nonce, pubkey)

        self.postdata = {
            'entry': 'weibo',
            'gateway': '1',
            'from': '',
            'savestate': '7',
            'useticket': '1',
            'pagerefer': "https://passport.weibo.com",
            'vsnf': '1',
            'su': su,
            'service': 'miniblog',
            'servertime': servertime,
            'nonce': nonce,
            'pwencode': 'rsa2',
            'rsakv': rsakv,
            'sp': password_secret,
            'sr': '1366*768',
            'encoding': 'UTF-8',
            'prelt': '115',
            "cdult": "38",
            'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
            'returntype': 'TEXT'  # 这里是 TEXT 和 META 选择,具体含义待探索
        }
        return sever_data

    def login(self):
        # 先不输入验证码登录测试
        try:
            sever_data = self.pre_login()
            login_url = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)&_'
            login_url = login_url + str(time.time() * 1000)
            login_page = self.session.post(login_url, data=self.postdata, headers=headers)
            ticket_js = login_page.json()
            ticket = ticket_js["ticket"]
        except Exception as e:
            sever_data = self.pre_login()
            login_url = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)&_'
            login_url = login_url + str(time.time() * 1000)
            pcid = sever_data["pcid"]
            self.get_cha(pcid)
            self.postdata['door'] = input(u"请输入验证码")
            login_page = self.session.post(login_url, data=self.postdata, headers=headers)
            ticket_js = login_page.json()
            ticket = ticket_js["ticket"]
        # 以下内容是 处理登录跳转链接
        save_pa = r'==-(\d+)-'
        ssosavestate = int(re.findall(save_pa, ticket)[0]) + 3600 * 7
        jump_ticket_params = {
            "callback": "sinaSSOController.callbackLoginStatus",
            "ticket": ticket,
            "ssosavestate": str(ssosavestate),
            "client": "ssologin.js(v1.4.19)",
            "_": str(time.time() * 1000),
        }
        jump_url = "https://passport.weibo.com/wbsso/login"
        jump_headers = {
            "Host": "passport.weibo.com",
            "Referer": "https://weibo.com/",
            "User-Agent": headers["User-Agent"]
        }
        jump_login = self.session.get(jump_url, params=jump_ticket_params, headers=jump_headers)
        uuid = jump_login.text

        uuid_pa = r'"uniqueid":"(.*?)"'
        uuid_res = re.findall(uuid_pa, uuid, re.S)[0]
        web_weibo_url = "http://weibo.com/%s/profile?topnav=1&wvr=6&is_all=1" % uuid_res
        weibo_page = self.session.get(web_weibo_url, headers=headers)

        # print(weibo_page.content.decode("utf-8")

        Mheaders = {
            "Host": "login.sina.com.cn",
            "User-Agent": agent
        }

        # m.weibo.cn 登录的 url 拼接
        _rand = str(time.time())
        mParams = {
            "url": "https://m.weibo.cn/",
            "_rand": _rand,
            "gateway": "1",
            "service": "sinawap",
            "entry": "sinawap",
            "useticket": "1",
            "returntype": "META",
            "sudaref": "",
            "_client_version": "0.6.26",
        }
        murl = "https://login.sina.com.cn/sso/login.php"
        mhtml = self.session.get(murl, params=mParams, headers=Mheaders)
        mhtml.encoding = mhtml.apparent_encoding
        mpa = r'replace\((.*?)\);'
        mres = re.findall(mpa, mhtml.text)

        # 关键的跳转步骤,这里不出问题,基本就成功了。
        Mheaders["Host"] = "passport.weibo.cn"
        self.session.get(eval(mres[0]), headers=Mheaders)
        mlogin = self.session.get(eval(mres[0]), headers=Mheaders)
        # print(mlogin.status_code)
        # 进过几次 页面跳转后,m.weibo.cn 登录成功,下次测试是否登录成功
        Mheaders["Host"] = "m.weibo.cn"
        Set_url = "https://m.weibo.cn"
        pro = self.session.get(Set_url, headers=Mheaders)
        pa_login = r'isLogin":true,'
        login_res = re.findall(pa_login, pro.text)
        # print(login_res)

        # 可以通过 session.cookies 对 cookies 进行下一步相关操作
        self.session.cookies.save()
        # print("*"*50)
        # print(self.cookie_path)

def weibo_comment():
    max_id = ""
    headers = {"user-agent": "mozilla/5.0 (windowS NT 10.0; win64; x64) appLewEbkit/537.36 (KHTML, likE gecko) chrome/71.0.3578.98 safari/537.36"}
    #加载cookie
    cookies = cookielib.LWPCookieJar("Cookie.txt")
    cookies.load(ignore_discard=True, ignore_expires=True)
    # 将cookie转换成字典
    cookie_dict = requests.utils.dict_from_cookiejar(cookies)
  
  while True:
        if max_id == "":
            url = "https://m.weibo.cn/comments/hotflow?id=4344811987373681&mid=4344811987373681&max_id_type=0"
        else:
            url = "https://m.weibo.cn/comments/hotflow?id=4344811987373681&mid=4344811987373681&max_id="+str(max_id)+"&max_id_type=0"
        # print(url)
        response = requests.get(url, headers=headers, cookies=cookie_dict)
        comment = response.json()

        if comment['ok'] == 0:
            break
        max_id = comment["data"]["max_id"]

        # print([data_1["text"] for data_1 in comment["data"]["data"]])
        line = []
        for comment_data in comment["data"]["data"]:
            data = comment_data["text"]
            p = re.compile(r'(<span.*>.*</span>)*(<a.*>.*</ a>)?')
            data = p.sub(r'', data)
            if len(data) != 0:
                line.append(data)
        time.sleep(1)
        print(line)

if __name__ == '__main__':
    username = "********"  # 用户名
    password = "********"  # 密码
    cookie_path = "Cookie.txt"  # 保存cookie 的文件名称
    weibo = WeiboLogin(username, password, cookie_path)
    weibo.login()
    weibo_comment()
效果就是这样子滴

大功告成! 晚餐要加鸡腿!

**未经本姑娘允许,禁止转载哈!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335