python爬取新浪微博话题的相关数据

python爬取新浪微博话题的相关数据

说在前头

说明: 获取的微博数据主要有昵称,认证,发布日期,微博内容,来源,转发数,点赞数,评论数,用户id,用户关注数,用户粉丝数,用户性别

python版本: Python version 3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]

本人小白~~
最近由于某些原因,我需要从新浪微博的话题上爬取一些数据,折腾了一天,最后终于爬到了想要的数据。
我没有使用爬虫框架(\捂脸) 原因是我…不会使用… 但是自己手写爬虫会更灵活,也让自己熟悉爬虫的很多细节~~
我选择一些我感觉有难度的地方讲,剩下的大家扫一眼代码就行。

过程

第一步 导入模块

需要说明的是,除了经典的 BeautifulSoup 网页解析库
我使用了神奇的 fake-useragent 这个随机生成各种 User-Agent 的库
爬取网页我使用的是 urllib.request

from bs4 import BeautifulSoup # 解析网页
from fake_useragent import UserAgent # 随机生成User-agent
import chardet  # 有时会遇到编码问题 需要检测网页编码
import re, urllib.request, socket, time, random, csv, json
socket.setdefaulttimeout(10) #这里对整个socket层设置超时时间。后续文件中如果再使用到socket,不必再设置 

第二步 设置代理

我使用的是西刺代理,通过解析网页获取代理池。

def get_proxy():
    # 报头设置
    def header(website):
        ua = UserAgent()
        headers=("User-Agent", ua.random)
        opener = urllib.request.build_opener()
        opener.addheaders = [headers]
        req = opener.open(website).read()
        return req
    # 读取网页
    proxy_api = 'http://www.xicidaili.com/nn'
    data = header(proxy_api).decode('utf-8')
    data_soup = BeautifulSoup(data, 'lxml')
    data_odd = data_soup.select('.odd')
    data_ = data_soup.select('.')
    # 解析代理网址 获取ip池(100个)
    ip,port = [],[]
    for i in range(len(data_odd)):
        data_temp = data_odd[i].get_text().strip().split('\n')
        while '' in data_temp:
            data_temp.remove('')
        ip.append(data_temp[0])
        port.append(data_temp[1])
    for i in range(len(data_)):
        data_temp = data_[i].get_text().strip().split('\n')
        while '' in data_temp:
            data_temp.remove('')
        ip.append(data_temp[0])
        port.append(data_temp[1])
    if len(ip) == len(port):
        proxy = [':'.join((ip[i],port[i])) for i in range(len(ip))]
        #print('成功获取代理ip与port!')
        return proxy
    else:
        print('ip长度与port长度不一致!')
proxy = get_proxy()

第三步 获取网页

实际操作的时候是先看网页,找到url,根据网页特征有针对性地来写函数的,但是这其实就是一个模板,基本的写法就是如下:

# 获取微博移动端网页
def get_data(url, proxy_addr):
    proxy = urllib.request.ProxyHandler({'http':proxy_addr})  
    opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
    headers={
        "User-Agent": UserAgent().random,
        'Cookie': '你的cookie'
    }
    opener = urllib.request.build_opener()
    opener.addheaders = [headers]
    data = json.loads(opener.open(url).read()) # 注意这句命令
    # data = opener.open(url).read() 
    # 一般用上面这个就行,但是微博有点特殊,返回的是类似字典的数据,所以就直接包装了,而且可以避开出现编码的问题
    time.sleep(2)
    yield data # 爬取的数据较多,使用生成器,减少占用内存

此外 在调试爬虫的时候,我在爬下来的网页的编码上遇到很大问题,一直提示 UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) ,然后我用 chardet 检测爬下来的网页是何种编码,发现的确是ascii码,并且无论怎样 decode 都无法正确显示中文( utf-8 和 gbk 都不行)。
后来我发现有两种解决办法:
- 由于微博手机版的 api 返回的数据是类似 python 嵌套的字典 / json,所以在写爬取网页内容的时候用 json 库直接处理包装成字典,然后函数的返回值就正显示中文了。否则,利用函数返回的网页内容再进行处理,就会返回上面的错误了。 实现的命令为: json.loads(opener.open(url).read())
- 第二种是更一般的,本质的处理方法。URL标准只会允许ascii字符(数字、字母和一部分符号),其他字符(比如汉字)是不符合URL标准的。解决办法是进行URL编码。在python3的 urllib.request 库中, urllib.request.quote 可以将不符合URL标准的字符编码成符合URL标准的字符,而相反的操作是 urllib.request.unquote ,这个命令可以把符合URL标准的字符解码成其他编码。

urllib.request.quote("http://www.baidu.com")
# 返回结果为 'http%3A//www.baidu.com',很明显冒号':'就经过url编码变成 '%3'

我爬取下来的内容是需要解码的,所以我应该使用 urllib.request.unquote 命令。


第四步 获取url

chrome浏览器:微博移动版网页

之前我一直用firefox浏览器,但是我打不开微博移动版,然后就去下载chrome浏览器了,我发现对微博移动版真的和友好。比如可以直接在网页版和移动版的微博中切换。
我发现微博的url不能通过传入 page= 数字 来翻页,折腾了好久,最后决定手动翻页,复制url链接。

坏处有两个:
- 每次用chrome浏览器 F12 调出开发者工具的时候,点最上面的工具栏第二个移动端图标的时候,翻到的页数是不一致的,有多有少,最少爬到200多条,不过我多次刷新尝试之后,可以爬到近1000条内容。
- … emm…碰到翻到很多页的时候,用鼠标去复制粘贴真的很累人…

(有些话题我翻到了50多个url,粘贴到心累)
粘贴下来的url链接就是这么长一串 ↓↓↓

url=['https://m.weibo.cn/api/container/getIndex?k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&from[]=526&from[]=526&_from_[]=huati_topic&_from_[]=huati_topic&jumpfrom=weibocom&containerid=100808994babd45bcf6a67177ccf76defc0224',
    'https://m.weibo.cn/api/container/getIndex?k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&from[]=526&from[]=526&_from_[]=huati_topic&_from_[]=huati_topic&jumpfrom=weibocom&containerid=100808994babd45bcf6a67177ccf76defc0224_-_main',
    'https://m.weibo.cn/api/container/getIndex?k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&k[]=%E5%B8%A6%E7%9D%80%E5%BE%AE%E5%8D%9A%E5%8E%BB%E8%A5%BF%E8%97%8F&from[]=526&from[]=526&_from_[]=huati_topic&_from_[]=huati_topic&jumpfrom=weibocom&containerid=100808994babd45bcf6a67177ccf76defc0224_-_main&page=1&since_id=%7B%22last_since_id%22:4143222894867825,%22res_type%22:1,%22next_since_id%22:4142783499850575%7D']

其间又有很多曲折!!!
我发现那些乱码的东西其实是你传入的 话题 名称,我通过下面的代码可以自由跳转到其他的话题 ~~~

var='带着微博去日本'
var=urllib.request.quote(var)
url='https://m.weibo.cn/api/container/getIndex?type=all&queryVal='+var+'&featurecode=20000320&luicode=10000011&lfid=106003type%3D1&title='+var+'&containerid=100103type%3D1%26q%3D'+var+'&page=1'

但是…我前面说过,即便修改 page 的值,也无法翻到下一页,只能抓到 10-20 条内容


第五步 解析网页

我需要的数据主要是:昵称,认证,发布日期,微博内容,来源,转发数,点赞数,评论数,用户id,用户关注数,用户粉丝数,用户性别

screen_name,Id,follow_count,followers_count,gender,verified,text,created_at,source,reposts_count,attitudes_count,comments_count = [],[],[],[],[],[],[],[],[],[],[],[]
pattern = "#.*?#" # 我用来清理掉微博内容中插入#话题#的
# 解析网页
for a in range(len(url)):
    for data in get_data(url[a], proxy[random.randint(3,50)]):
        try:
            for i in range(len(data['cards'])):
                try:
                    for j in range(len(data['cards'][i]['card_group'])):
                        try:
                            content = re.sub(pattern,'',BeautifulSoup(data['cards'][i]['card_group'][j]['mblog']['text'], "lxml").get_text()).strip()
                            if content not in text:
                                try:
                                    screen_name.append(data['cards'][i]['card_group'][j]['mblog']['user']['screen_name'])
                                    Id.append(data['cards'][i]['card_group'][j]['mblog']['user']['id'])
                                    follow_count.append(data['cards'][i]['card_group'][j]['mblog']['user']['follow_count'])
                                    followers_count.append(data['cards'][i]['card_group'][j]['mblog']['user']['followers_count'])
                                    gender.append(data['cards'][i]['card_group'][j]['mblog']['user']['gender'])
                                    verified.append(data['cards'][i]['card_group'][j]['mblog']['user']['verified'])
                                    text.append(content)
                                    created_at.append(data['cards'][i]['card_group'][j]['mblog']['created_at'])
                                    source.append(data['cards'][i]['card_group'][j]['mblog']['source'])
                                    reposts_count.append(data['cards'][i]['card_group'][j]['mblog']['reposts_count'])
                                    attitudes_count.append(data['cards'][i]['card_group'][j]['mblog']['attitudes_count'])
                                    comments_count.append(data['cards'][i]['card_group'][j]['mblog']['comments_count'])

                                except:
                                    pass
                        except:
                            pass
                except:
                    pass
            print("已解析%d,未解析%d,共%d链接..." %(a+1, len(url)-a-1, len(url)))
        except:
            pass
print("All done!")

乍一看没有获取网页,其实我写在循环里面了,因为函数用生成器 yield 返回了可迭代对象,所以循环起来不卡,而且不算慢。
你会看见许多 try...except... 语句,其实一开始我没有写这么多的,我是通过跑了一遍遍渐渐把这个解析函数完善的,跑出来应该没有问题。
缩进的语法在这里就不是辣么实用了,一看走眼就会出错。不过我用的是 jupyter notebook ,按住 alt 键加上鼠标多行点选的感觉堪比 sublimetext3 呢~~

跑出来的结果:
解析过程

抓取的数据(部分):

解析结果


当然,也可以获取 话题描述,用户认证描述 之类的数据,具体可以去看 返回的网页字典的键。

嘻嘻&& 我知道你懒,所以给你们找好啦~~

用户的相关信息在 data['cards'][0]['card_group'][0]['mblog']['user'] 下面。

data['cards'][0].keys() 
# dict_keys(['card_type', '_appid', '_cur_filter', 'title', 'show_type', 'buttontitle', 'scheme', 'hide_oids', 'card_group'])

data['cards'][0]['card_group'][0].keys() 
# dict_keys(['card_type', 'itemid', 'show_type', 'scheme', 'mblog'])

data['cards'][0]['card_group'][0]['mblog'].keys() 
#dict_keys(['created_at', 'id', 'mid', 'idstr', 'text', 'textLength', 'source', 'favorited', 'thumbnail_pic', 'bmiddle_pic', 'original_pic', 'is_vip_paid_status', 'is_paid', 'mblog_vip_type', 'user', 'picStatus', 'reposts_count', 'comments_count', 'attitudes_count', 'isLongText', 'visible', 'rid', 'mblog_show_union_info', 'is_controlled_by_server', 'timestamp_text', 'expire_after', 'page_info', 'bid', 'pics'])
# 话题描述
description[area[a]]=data['pageInfo']['desc_more'][0] 
# '阅读1.8亿\u3000讨论7.7万\u3000粉丝2623'

第六步 写入csv

# 数据写入csv
with open('西藏.csv','w',encoding = 'utf-8', newline = '') as f:
    w = csv.writer(f)
    w.writerow(['screen_name','verified','created_at','text','source','reposts_count',
                'attitudes_count','comments_count','Id','follow_count','followers_count','gender'])
with open('西藏.csv', 'a+', encoding = 'utf-8') as f:
    w = csv.writer(f, lineterminator='\n')
    for i in range(0, len(text)):
        try:
            w.writerow([screen_name[i],verified[i],created_at[i],text[i],source[i],reposts_count[i],attitudes_count[i],
                       comments_count[i],Id[i],follow_count[i],followers_count[i],gender[i]])
        except Exception as e:
            print('第 ', i , text[i],'error: ', e, '\n')
print("成功写入文件!")

P.S:
我用Excel打开之后是乱码的:

csv乱码

原因是编码问题,解决办法是csv文件用记事本打开,然后 另存为 ,选择 unicode 编码 ↓

csv乱码解决办法

然后再用Excel打开就是

csv正常


结语

就这样吧 ~~~
难得今天有空就洋洋洒洒写了这么多
第一次在这里写文章
也是我第一次写科技类的经验文章
谢谢!

猜你喜欢

转载自blog.csdn.net/m0_37952030/article/details/77505724