使用Python爬虫玩转微博 如何拥有庞大的粉丝数

鳄鱼君

发表文章数:643

热门标签

,

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python » 使用Python爬虫玩转微博 如何拥有庞大的粉丝数

废话不多少,直接介绍要实现的功能:
● weibo_login.py:微博用户登录,同时也是程序运行文件。
● weibo_verify_code.py:第三方平台API,实现验证码识别。
● weibo_collect.py:根据关键字搜素并采集热门微博。
● weibo_send.py:发布微博。
● weibo_follow.py关注用户
● weibo_forward.py微博点赞和转发评论
● data.csv:存储采集数据。
● 文件夹video和image:分别存储采集的视频和图片。

首先要做的就是建立好结构框架,接下来逐个编写实现代码!

用户登录

进入微博首页,我们发现微博大部分功能都要用户登录才能使用。那么爬取微博的第一步就是实现用户登录。在Chrome浏览器对微博的登录机制进行分析,在浏览器中输入http://weibo.com/,打开开发者工具,捕捉首页的请求信息,这里需要开发者熟练的使用Chrome开发工具

在开发者工具里分别查看XHR、JS和Doc标签的请求信息,一般都是从这里突破,登录的话可以直接搜索login关键词,这只是一种思路,一番寻找后发现以下js文件响应内容比较可疑:
使用Python爬虫玩转微博 如何拥有庞大的粉丝数
使用Python爬虫玩转微博 如何拥有庞大的粉丝数
● 请求参数su:代表用户账号,一般以su或username命名。
● 请求参数1591576284351:以“159”开头的数字大多数是时间戳。
● 请求参数rsakt和响应内容rsakv:无法确定这两个参数代表的含义,但两者都含有rsa, rsa是一个加密方法。参数值可能经过加密处理。
● 响应内容pubkey:中文翻译为公共密钥,从这个参数可知,某些数据肯定做过加密处理,大多数是对账号、密码做加密处理。

以上信息是没有登录之前发现的,在用户登录之前会触发一个准备登录(prelogin)请求,该请求中包含一些加密信息。也就是说,在实现登录功能之前,先要对上述请求信息发送请求,获取其响应内容的加密信息后,才能进行下一步用户登录。实现代码如下:

你也可以先登录,寻找发送登录请求的参数。这里需要使用请求暂停大法,什么意思呢?登录新浪微博的时候,页面会刷新,也就是发送登录的请求会很快的被刷新掉,所以在登录成功之后,暂停抓取网页的请求,这时会找到登录的请求:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

从这个图片往前分析,要想登录就需要构造参数,对于登录请求的参数,做一下分析:su、sp、servertime、nonce和rsakv是动态变化的,其它都是不变的,那么现在就需要构造这些动态变化的参数,实现登录。接下来就是寻找参数的加密方式,这需要稍微了解一下JavaScript的有关知识。这里还是使用全局搜索的方式,搜索su、sp、servertime、nonce等等这些参数,随便试试,最终会发现在:https://js1.t.sinajs.cn/t5/register/js/v6/pl/register/loginBox/index.js对参数进行了加密,稍微分析一下:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数
使用Python爬虫玩转微博 如何拥有庞大的粉丝数

(1)用户账号su主要使用base64方式加密。
(2)密码是使用RSA加密的,rsapubkey和文件中的‘10001’, 这两个值需要先从16进制转换成10进制,把10001转成十进制为65537,随后加入servertime和nonce再次加密。

以上是通过js代码猜想的,鳄鱼君Ba对于js也不是太了解。滑稽,其实是百度的!。现在分别对两者定义不同的函数,代码如下:

# 账号加密
def get_su(username):
    # 对email地址或手机号的特殊符号编码 javascript 中 encodeURIComponent
    #使用urllib.parse.quote_plus
    # 然后使用base64加密
    username_quote=urllib.parse.quote_plus(username)
    username_base64=base64.b64encode(username_quote.encode('utf-8'))
    return username_base64.decode('utf-8')
# 密码加密,数据来自函数get_server_data
def get_password(password,servertime,nonce,pubkey):
    rsaPubkey=int(pubkey,16)
    # 创建公钥
    key=rsa.PublicKey(rsaPubkey,65537)
    # 拼接明文
    message=str(servertime)+'\t'+str(nonce)+'\n'+str(password)
    message=message.encode('utf-8')
    # 加密
    password=rsa.encrypt(message,key)
    # 将加密信息转换为16进制
    password=binascii.b2a_hex(password)
    return password

rsa模块是第三方库,可使用pip install rsa安装。

知道了登录请求的参数和加密后的用户名和密码,现在就可以发送登录请求:

def login(username,password):
    # 获取servertime、nonce、rsakv,su和sp
    su=get_su(username)
    server_data=get_server_data(su)
    servertime=server_data['servertime']
    nonce=server_data['nonce']
    pubkey=server_data['pubkey']
    rsakv=server_data['rsakv']
    sp=get_password(password,servertime,nonce,pubkey)
    # 构造请求参数
    postdata = {
        'entry': 'weibo',
        'gateway': '1',
        'from': '',
        'savestate': '7',
        'useticket': '1',
        'pagerefer': "http://login.sina.com.cn/sso/logout.php?entry=miniblog&r=http%3A%2F%2Fweibo.com%2Flogout.php%3Fbackurl",
        'vsnf': '1',
        'su': su,
        'service': 'miniblog',
        'servertime': servertime,
        'nonce': nonce,
        'pwencode': 'rsa2',
        'rsakv': rsakv,
        'sp': sp,
        'sr': '1366*768',
        'encoding': 'UTF-8',
        'prelt': '115',
        'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
        'returntype': 'META'
    }
    # 用户登录
    url='http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
    login_page=session.post(url,data=postdata)
    print(login_page.content.decode('gbk'))
    # login_url=re.findall('location.replace\(\'(.*?)\'\);',login_page.text,re.S)[0]
    # login_idnex=session.get(login_url,headers=headers)
    print(login_idnex.text)
if __name__ == '__main__':
    #构造请求头
    headers={
        "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
    }
    # 代理IP
    proxies={}
    # 新建回话
    session=requests.session()
    # 用户账号
    login('鳄鱼君Ba','XXXXXXXXX')

如果不开启微博的双重认证,登录是不会出现验证码的,想要突破验证码需要开启认证接着分析。这里用户登录微博的时候,会重定向跳转,所以如果需要找到重定向后的地址。

# 用户登录
url='http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
login_page=session.post(url,data=postdata)
# 找到重定向后的地址
login_url=re.findall('location.replace\(\'(.*?)\'\);',login_page.text,re.S)[0]
login_idnex=session.get(login_url,headers=headers)
print(login_idnex.text)

这时候再次运行代码,会打印一串信息,看似是失败,其实不然,你会发现其中包含userinfo的键值对、uniqueid。我们登录后用户首页进行抓包发现:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

其中userinfo代表用户信息,uniqueid等于上图的uidoid,因此根据uniqueid获取用户首页信息,对响应的文本提取想要的内容,并构造用户首页:

 # 用户登录
    url='http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)'
    login_page=session.post(url,data=postdata)
    login_url=re.findall('location.replace\(\'(.*?)\'\);',login_page.text,re.S)[0]
    login_idnex=session.get(login_url,headers=headers)
    uuid=login_idnex.text
    uuid_data=re.findall('"uniqueid":"(\d+)"',uuid,re.S)[0]
    # 根据niqueid构造微博首页的URL
    wei_bo_url='https://weibo.com/%s'%uuid_data
    wei_bo_data=session.get(wei_bo_url,headers=headers)
    response=wei_bo_data.text
    # 提取用户信息
    person_info={}
    if '$CONFIG' in response:
        person_info['nick']=response.split("$CONFIG['onick']=")[1].split(';')[0]
        person_info['watermark']=response.split("$CONFIG['watermark']=")[1].split(';')[0]
        person_info['location']=response.split("$CONFIG['location']=")[1].split(';')[0]
        person_info['domain']=response.split("$CONFIG['domain']=")[1].split(';')[0]
    print(person_info)

到这里就实现了用户登录,获取了用户的相关信息,费这么大麻烦,就是为了寻找uniqueid这个值。以上代码均存放在weibo_login.py文件中。

用户登录(验证码)

开启双重认证,就会出现图片验证码,记得先开启哦!这里还是寻找图片请求的URL,这个非常好找,先清空所有的URL请求,点击更换验证码,新增的请求就是图片验证码的请求数据:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

可以看到有三个请求参数,自己可以找一下图片上的请求看一下:r是一个随机数,生成的规律不固定;s是一个固定数字;p是一个不可知的数据,需要找出该数据的来源。再分析登录前的加密信息(prelogin)是否也发生变化

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

发现带验证码的登录前加密信息中的数据多了showpinis_openlocklm;pcid的数据与p参数的值相同。

结合上述分析:
(1)访问加密信息,根据返回内容进行判断,如果存在showpin、is_openlock和lm数据,就说明当前登录需要验证码识别。
(2)如果存在验证码,就先下载验证码图片,再进行下一步的用户登录;否则直接执行login函数。

下载验证码图片的代码如下:

import random
from PIL import Image
def get_img(pcid):
    url='https://login.sina.com.cn/cgi/pin.php?r=%s&s=0&p=%s'%(str(int(random.random() * 100000000)),pcid)
    resp=session.get(url)
    verify_code_path='./a.png'
    with open(verify_code_path,'wb') as f:
        f.write(resp.content)
    try:
        img=Image.open('./a.png')
        img.show()
        img.close()
    except:
        print('请找到当前目录下的a.png文件,获取验证码图片')

函数get_img()中,参数pcid由加密信息的pcid传递,最后函数返回的是图片的相对路径。完成验证码下载后,接着分析带验证码的登录请求。

在前面用户登录的请求参数里,你发现有个pcid和door,这个不出验证码是没有的,由于我登录了很多次,所以上面的参数中就有pcid和door。pcid是来自加密信息里的响应数据,door是验证码图片内容。

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

根据上述分析,总结如下:
(1)如果带有验证码,加密信息的响应内容就含有showpin、is_openlock和lm数据。
(2)判断加密信息的响应内容是否需要下载验证码图片。
(3)下载图片验证码后,需要对验证码进行识别。
(4)在用户登录请求中,带验证码的登录需要添加参数pciddoor

在上面的分析要点中,目前还没解决验证码识别的问题。这里可以使用第三方打码平台,例如超级鹰、打码兔等等都可以,由于鳄鱼君Ba的积分用完了,这里暂且使用手工打码的方式,如果想要了解如何使用第三方打码平台的话,可以参考:借助打码平台进行验证码识别进行模拟登录,这里不过多介绍!在login函数中添加判断:

# 判断是否存在验证码
if 'is_openlock' in server_data.keys():
    # 添加请求参数
    pcid=server_data['pcid']
    postdata['pcid']=pcid
    # 下载图片验证码
    get_img(pcid)
    # 手动识别
    code=input('请输入验证码:')
    postdata['door']=code
# 用户登录

到这里登录基本就实现了,接下来该重点内容了!

关键词搜索

我们需要实现关键词搜索热门微博,这样我们才能掌握微博最新的动态走向。打开微博搜索,随便搜索一个关键词:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

主要寻找翻页的请求,这里非常好找的,根据URL规律发现请参数如下:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

然后分析请求信息:
● URL含有“%23”等字符,说明URL对中文进行了编码处理,编码处理由urllib.parse.quote()实现。
● Refer的参数值是固定不变的。
● page代表页数,一个关键词最多返回50页内容。

分析该请求的响应内容,这里涉及到数据提取,具体不再详细分析,比较简单。新建weibo_collect.py文件,在里面编写微博采集代码:

import csv,os,requests
import urllib.parse
import datetime
from lxml import etree
from concurrent.futures import ThreadPoolExecutor
def thread_video(get_video_value,video_path):
    # 获取视频URL地址
    url=get_video_value.split('&video_src=')[1]
    url='http:'+urllib.parse.unquote(url)
    try:
        temp_value=requests.get(url)
        if not os.path.exists(video_path):
            with open(video_path+'.mp4','wb') as f:
                f.write(temp_value.content)
    except:
        pass
def thread_img(k,img_path):
    if not os.path.exists(img_path):
        res=requests.get('http:'+k)
        with open(img_path,'wb') as f:
            f.write(res.content)
# 采集微博
def collection(keyword,session,pagenumber):
    # 关键词编码
    now=datetime.datetime.now().strftime('%Y-%m-%d')
    keyword=urllib.parse.quote(keyword)
    # 构建URL
    url='https://s.weibo.com/weibo?q=%s&Refer=index&page=%s'% (keyword,pagenumber)
    res=session.get(url)
    # 清洗多余的符号
    get_value=res.text.replace('\u200b','')
    tree=etree.HTML(get_value)
    content_info=tree.xpath('//div[@class="content"]')
    item={}
    if content_info:
        for i in content_info:
            item['title']=i.xpath('./div[1]/div[2]/a/text()')[0]
            item['content_text']=''.join([x.strip() for x in i.xpath('./p//text()') if len(x.strip())>0]) # 去掉空格和换行符,拼接列表
            # 输出图片
            img_path=''
            item['content_img']=i.xpath('.//ul[contains(@class,"m")]/li/img/@src')
            if len(item['content_img'])>0:
                for k in item['content_img']:
                    img_path='image/'+k.split('/')[-1]
                    img_pool=ThreadPoolExecutor()
                    img_pool.submit(thread_img,k,img_path)
            # 输出视频
            video_path=''
            item['content_video']=i.xpath('.//a[contains(@class,"WB_video_h5")]/@action-data')
            if len(item['content_video'])>0:
                for v in item['content_video']:
                    video_path='video/'+v.split('&video_src=')[1].split('%2F')[3].split('.')[0]
                    video_pool=ThreadPoolExecutor()
                    video_pool.submit(thread_video,v,video_path)
                    thread_video(v,video_path)
             # 生成csv
            with open('data.csv','a',newline='',encoding='utf-8') as f:
                writer=csv.writer(f)
                writer.writerow([item['title'],item['content_text'],img_path,video_path,now])

整段代码共由以下三个函数组成:
● thread_img():多线程下载图片。
● thread_video():多线程下载视频。
● collect():实现微博采集,函数参数keyword、session和pagenumber分别是关键字带有用户信息的会话对象采集页数。代码存放在文件weibo_collect.py中,与用户登录(weibo_login.py)的代码不在同一个文件,所以需要将带有用户信息的会话对象传递给该函数。

函数collect()实现的功能依次如下:
● 对函数参数keyword执行一次URL编码。
● 构建请求链接并发送请求,并对响应内容进行清洗处理。
● 采集微博用户信息、文字内容、图片和视频。图片和视频的下载分别调用函数thread_img()和thread_video(),使用多线程下载文件,提高爬取速度。

代码存放在文件weibo_collect.py中,并且与文件weibo_login.py同一目录,当前目录下必须有文件夹imagevideo,否则下载图片和视频无法保存。修改weibo_login.py文件,修改的代码如下:

if __name__ == '__main__':
    #构造请求头
    headers={
        "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
    }
    # 代理IP
    proxies={}
    # 新建回话
    session=requests.session()
    # 用户账号
    user_info=login('13673770772','1897745389dsgmmc')
    keyword='#情人舞挑战#'
    pagenumber=1
    # 导入微博采集功能
    from weibo_collect import collection
    collection(keyword, session, pagenumber)

发布微博

在浏览器上编辑好要发布的内容,然后单击“发布”按钮进行发布。微博中有很多可以编辑的功能,如插入表情、话题、图片、视频和定时发送等。其中,表情和话题可以归纳为文字内容。这篇文章主要实现文字内容、图片和定时发送的微博发布。

在浏览器上分别捕捉三种不同发布方式的请求如下图,请求的URL都是相同的,只是参数不同,分别对应:文字内容,图片和定时发送:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数
使用Python爬虫玩转微博 如何拥有庞大的粉丝数
使用Python爬虫玩转微博 如何拥有庞大的粉丝数

请求参数的差异如下:

(1)参数updata_img_num,该参数是所发布的图片的数量。参数pic_id的值非空,从参数名分析可知,参数pic_id应该是图片的id。
(2)参数addtime,该参数是发布时间;再对比两者的参数pic_id和updata_img_num,参数pic_id将每张图片之间用“|”隔开。
(3)参数locationtext分别是用户信息和发布的文字内容,其他参数都是固定不变的。

经过上述分析,现在无法确定pic_id的数据来源,该参数如果是图片的id,那么在添加图片的时候,网站应该会对添加的图片生成一个图片id,用于标识图片。为了验证猜想,我们捕捉添加图片时所触发的请求信息:

使用Python爬虫玩转微博 如何拥有庞大的粉丝数

请求链接是GET请求,请求方法是POST请求,而且请求参数已在请求链接上,说明该请求POST的数据不是请求参数,而是POST图片文件,参数是固定的,我们只需要获取响应的内容即可。

图片上传POST的数据是b64_data,该数据是使用base64对图片的字节流加密而成的。综合上述分析,微博发布需要实现两部分功能:第一是实现图片上传,获取图片id;第二是根据条件判断选择微博发布方式。代码如下:

import requests
import base64
import time
def upload_pic(session,watermark,nick,file_list):
    pic_id_list=[]
    #判断图片数量是否在1-9之间
    if len(file_list)>0 and len(file_list)<10:
        for i in file_list:
            url='https://picupload.weibo.com/interface/pic_upload.php?cb=https%3A%2F %2Fweibo.com%2Faj%2Fupimgback.html%3F_wv%3D5%26callbacl%3DSTK_ijax'+str(int(time.time()*100000))+'&mime=image%2Fjpeg&data=base64&url=weibo.com%2F'+watermark+'&markpos1&logo=1&nick=%40'+nick+'&marks=0&app=miniblog'
            # 图片以字节流数据读取,然后以base64加密
            files={'b64_data':base64.b64encode(open(i,'rb').read())}
            res=session.post(url,files=files)
            get_picid=eval(res.text.split('</script>')[1])['data']['pics']['pic_1']['pid']
            pic_id_list.append(get_picid)
    return pic_id_list
def send(session,watemark,location,value,pic_id_list,addtime=''):
    # 构造请求头
    headers={
        "Referer":"https://weibo.com/eyujunBa/home?topnav=1&wvr=6",
        "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"
    }
    # 构造请求参数
    data={
       "location":location,
        "text":value,
        "appkey":"",
        "style_type":"1",
        "pic_id":"",
        "tid":"",
        "pdetail":"",
        "mid":"",
        "addtime":addtime,
        "isReEdit":"false",
        "gif_ids":"",
        "rank":"0",
        "rankid":"",
        "module":"stissue",
        "pub_source":"main_",
        "pub_type":"dialog",
        "isPri"	:"null",
        "_t":"0",
    }
    if pic_id_list:
        pic_id=''
        for i in pic_id_list:
            pic_id+=i+'|'
        # 去除最后的|
        if pic_id[-1]=='|':
            pic_id=pic_id[0:len(pic_id)-1]
        data['updata_img_num']=len(pic_id_list)
        data['pic_id']=pic_id
    # 构建URL
    url='https://weibo.com/aj/mblog/add?ajwvr=6&__rnd=%s'%(int(time.time()*1000))
    res=session.post(url,data=data,headers=headers)
    if res.status_code==200:
        return True
    else:
        return False

实现的功能代码,存放在文件weibo_send.py中,整段代码由以下两个函数组成:
● upload_pic():实现图片上传。函数参数session、watermark、nick和file_list:
———— session是带有用户登录状态的会话对象。
———— watermark和nick是用户信息。
———— file_list是图片列表,列表元素是图片路径。
● send():实现微博发布。函数参数有session、watermark、location、value、addtime和pic_id_list:
———— session是带有用户登录状态的会话对象。
———— watermark和location是用户信息。
———— value和addtime是发布内容和发布时间。
———— pic_id_list是函数upload_pic()返回的图片id列表。

send()功能说明如下:
(1)需要重新设置请求头并加入Referer信息,否则会导致发送失败,因为网站做了检测Referer的反爬虫机制。
(2)请求参数合并了三种不同的发布方式,例如只发布文字内容,只需将参数pic_id和addtime的值设置为空即可。若发布图片,在设置pic_id的参数值时,则会相应地创建参数updata_img_num。
(3)请求链接最后的一串数字是当前时间的时间戳再乘以1000后取整所得。

修改微博登录文件weibo_login.py,代码如下:

if __name__ == '__main__':
    #构造请求头
    headers={
        "User-Agent":"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
    }
    # 代理IP
    proxies={}
    # 新建回话
    session=requests.session()
    # 用户账号
    user_info=login('鳄鱼君Ba','www.e1yu.com')
    keyword='#情人舞挑战#'
    pagenumber=1
    # 导入微博采集功能
    from weibo_collect import collection
    # collection(keyword, session, pagenumber)
    # 导入微博上传
    from weibo_send import send,upload_pic
    # 获取用户信息
    watermark=user_info['watermark']
    nick=user_info['nick']
    location=user_info['location']
    file_list=['a.png',]
    upload_pic(session,watermark,nick,file_list)
    # import os
    pic_id_list =upload_pic(session,watermark,nick,file_list)
    # 发布微博
    send(session,watermark,location,"坤坤",pic_id_list,addtime='')

以上代码就已经实现了微博发布功能,对于上传图片的加密方式,需要通过fiddler抓包在才能获取真实的地址。在微博采集的时候,我们存储的图片是加密过后的,这里也可以使用采集过后的图片发布,不需要上传图片!

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《使用Python爬虫玩转微博 如何拥有庞大的粉丝数》 发布于2020-06-07

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

6 + 4 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册