首页 » 技术分享 » 微博评论爬取

微博评论爬取

 

Emmmm中午帮陈大王做了一个微博评论的爬虫,献出了人生爬虫处女秀hhh

然后就想顺手把这个博客写了,QAQ

 

新浪微博的接口可能换了几代了,所以网上的博客大多数都已经失效了

另外据多篇博客说宁愿爬移动端也不要爬PC段的微博,差异应该主要体现在参数的复杂度和反爬虫机制上

总体来说移动端还是比较好爬取的

 

先说下整体思路

一般来说首先就是想方设法千方百计把数据获取来源的api地址找到,这样我们向该地址发出请求才能获取我们想要的数据,这一步基本没有规律可循(靠猜靠观察靠百度靠缘分),比如微博评论最新的获取API我成功的从一篇博客get到了,大部分博客已经都不行了......

当有目标API后我们需要写出相应的代码来获取返回的json(在此微博评论API涉及到翻页,具体逻辑为max_id_type=0时返回第一页,否则根据max_id参数的值返回第xx页,这个查询过程只能顺序,不能倒序)。拿到json后需要观察json的格式,用相关python库把json解析,把自己想要的数据以合适的形式提取出来,在根据自己的需要存储数据。这样一个完整的爬虫就基本完成了。

一般来说爬虫的难点不在这些,难在应对一些大厂站点的反爬虫机制。

 

直接上代码

# -*- 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
import xlwt

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():
	comments = []
	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=4397315030686217&mid=4397315030686217&max_id_type=0"
		else:
			url = "https://m.weibo.cn/comments/hotflow?id=4397315030686217&mid=4397315030686217&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"]
		comments_ = dfs(comment["data"]["data"])
		for c in comments_:
			comments.append(c)
		time.sleep(1)
		# print(line)
	print(comments.__len__())
	book = xlwt.Workbook()
	sheet = book.add_sheet('sheet1')
	for i,line in enumerate(comments):
		sheet.write(i,0,line)
	book.save('comments.xls')

def dfs(lines):
	comments=[]
	for line in lines:
		data = line['text']
		p = re.compile(r'(<span.*>.*</span>)*(<a.*>.*</ a>)?')
		data = p.sub(r'', data)
		if len(data) != 0:
			comments.append(data)
		try:
			sons = dfs(line['comments'])
			for son in sons:
				comments.append(son)
		except Exception:
			pass

	return comments

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

其中为了爬取评论的子评论,代码做了递归处理

关于api:

第一页URL为https://m.weibo.cn/comments/hotflow?id=4397315030686217&mid=4397315030686217&max_id_type=0

之后URL为https://m.weibo.cn/comments/hotflow?id=4397315030686217&mid=4397315030686217&max_id=*******

其中id和mid需要换成目标微博相对应的id、mid,方法如下:

示例链接:https://m.weibo.cn/detail/4397315030686217#comment

如图,用chrome打开移动端的web页面,打开F12找到nerwork中获取评论的api,把两个id复制下来替换到代码中。

另外只需要在代码中替换自己的新浪微博用户名和密码即可

 

爬取下来的评论都存放到了Excel文件中。

 

不过有一个bug是不知道是不是因为这是移动端API的缘故,后端提供的api接口返回的评论是不全的,该示例中960条评论只返回的360条。最后只好采用了飞哥公司内部工具直接爬下来的json。

不过比较奇怪的是这个json的key居然没有引号(好迷)

所以这里对json用正则表达式做了处理:

js = re.sub(r'(?!={|, )(\w*):\s', r'"\1":', js)

之后又另写了一份代码单独处理这个json,解析完放进Excel,代码就不放了......

 

最终配合老王的词云分析,大概效果:当当当当(逃

转载自原文链接, 如需删除请联系管理员。

原文链接:微博评论爬取,转载请注明来源!

0