昨天,我说我会把之前写过的爬虫代码免费分享给大家,后来因为一些原因(公司写的代码不能分享出去?)不得不停止这种源码分享的方式。
先在这向大家说声:抱歉。
于是我想,我还是一个平台一个平台地做系统的分享。
今天我们要聊的是微博的数据采集。
一、找出微博主页唯一标示oid
一般做爬虫爬取网站,首选的都是m站,其次是wap站,最后考虑PC站,因为PC站的各种验证最多。当然,这不是绝对的,有的时候PC站的信息最全,而你又恰好需要全部的信息,那么PC站是首选。一般m站都以m开头后接域名, 这里通过m.weibo.cn去分析微博的HTTP请求。
首先在PC端打开杭州市旅游委员会的微博主页:
https://weibo.com/hangzhoutourism?topnav=1&wvr=6&topsug=1&is_hot=1
接着打开调试页面(Chrome浏览器是右击检查,其他大部分浏览器点击直接按F12),先选中右侧手机模式,然后刷新页面,这个时候地址栏上的URL就变成m站的微博主页地址,如下图1所示。
注意:当打开一个页面,再点开Network标签时是不会有信息的,这时需要在打开的情况下,刷新一下页面。
https://m.weibo.cn/p/1005051789342195?sudaref=login.sina.com.cn&display=0&retcode=6102
也有些微博主页对应的m站地址是这样形式。
https://m.weibo.com/u/2189067512
图1 微博主页及调试页面
这里的1005051789342195和2189067512就是没个微博主页的唯一标示,但是这个唯一标示为什么有些是16位的数字,有些是10位的数字呢?不妨继续在PC上进行观察。
如下图2所示,进入调试页面打开Network(注意不是手机模式),选择Doc,从返回的HTTP包中找到oid,这个oid为1789342195。通过多次抓包分析不同的微博主页,会发现以p打头的类似“p/1005051789342195”,它们的oid都是除掉100505之外的其他10位数字,而以u打头的类似“u/2189067512”,它们的oid就是u后面的10位数字。
图2 PC模式的调试页面
二、通过ajax分析法找出用户微博内容的API
找到微博用户主页的唯一标示oid之后,接下来要做的就是使用ajax分析法找出微博用户发布的微博内容信息的API。如下图3所示,打开调试页面,选中手机模式,然后刷新页面,在Network搜索API,即选择XHR进行过滤,发现有两个已经发送的API请求(API请求一般都在XHR中,其他网页请求在Doc中)。
图3 在XHR找出请求数据的API
查看这两个API返回的数据发现,第一个API返回的是用户数据,第二个API返回的是微博内容数据。以同样的方式分析大量的请求微博数据的API发现它们这个API的核心参数是containerid,并且这个containerid值为固定6位数字107603和微博用户主页的唯一标示oid组合而成。
https://m.weibo.cn/api/container/getIndex?containerid=1076031789342195
接下来就是分析API返回数据,如图6-4所示,在右边选择Preview预览返回的JSON格式的数据,点击data下的cards中任选一个card,其中的mblog标签下就有需要的微博内容数据。
图4 分析API返回的JSON格式的数据
如下图5所示,我们继续观察发现这个JSON中只有12条数据。
图5 第一次请求只有12条数据
如果需要获取更多的数据应该怎么办呢?往下滑动到下一页,继续查看请求的API内容,如下图6所示。
图6 滑动到下一页
发现在获取下一页数据时的API添加了一个新的参数page,值为2。继续往下翻页,page会变成3、4、5一直到滑动到最后一页。由此可以推断这个API获取哪一页的数据由page决定。
三、分析返回的JSON格式的微博内容
通过数据请求的API获取到返回的微博内容,以其中一个card来分析获取到的数据信息。如图7所示,发现微博内容数据是在mblog中。
图7 JSON格式数据
通过和微博网页上的内容对比,可以推断出text为微博标题内容,created_at为微博发布时间,id为该微博的详情地址中的区分数值(后面会说),而图片信息需要根据是图文内容还是视频内容的不同,获取方式也是不一样,分析如下。
如果card_type为9时,该card下面的内容就是正常的图文或视频内容。如果card_type为非9时,card下的内容就可能会是广告信息。如图8所示。
图8 card_type不为9时
如果该微博内容为普通图文内容时,内容的图片信息会在mblog下的pics中,如图9所示。
图9 图文内容中的图片信息
如果该微博内容为视频时,视频的封面图片信息在mblog/page_info/page_pic下的url对应的值,如图10所示。
图10 视频内容下的视频封面图信息
以上所列举的都是原创微博的情况,那么如果是转发微博的话JSON格式是怎么样的呢。首先我们先确定cards下的内容哪些是转发的哪些是原创的,确定技巧就是判断mblog下是否有retweeted_status元素,如果有则表示是转发内容,否则是原创内容,如图11所示。
图11 转发或原创内容的标示
对于转发内容,我们如何获取被转发内容的标题、发布时间及图片信息呢?如图12所示,发现对应的值是retweeted_status下的text为微博标题内容,created_at为微博发布时间,id为该微博的详情地址中的区分数值,pics为图片信息。
图12 转发图文内容
如果是转发视频内容的话,对应的视频的封面图片信息在mblog/retweeted_status /page_info/page_pic下的url对应的值,标题和发布时间信息在mblog/retweeted_status下的text和created_at,如图13所示。
图13 转发的视频信息
最后要获取的是微博的详情页面地址,前面我们在mblog下发现有一个id元素。通过多条微博的数据进行验证,可以发现微博详情页的地址为m.weibo.cn/status/+id,这个id也就是返回的JSON数据中的mblog/id。如图14所示。
图14 微博的详情页地址
四、获取微博内容的代码实现
分析完接口之后就可以开始编写爬虫代码。
首先需要拼接出完整的可以获取到JSON数据的API路径。通过前面的分析得出API中的containerid是由用户的唯一标示oid拼接而成的,并且获取方式需要分两种情况,有些用户微博主页是https://m.weibo.com/p/1005051789342195,还有些是https://m.weibo.com/u/2189067512。对于前者,它的oid是100505之后的值,后者的oid是u后面的数字。
基于这个逻辑编写获取API路径的代码。
start_url = 'https://m.weibo.com/p/1005051789342195'
或者
start_url = 'https://m.weibo.com/u/2189067512'
containerid = ''
url_head ='https://m.weibo.cn/api/container/getIndex?containerid='
if'https://m.weibo.cn/p/' in start_url:
containerid = start_url.replace('https://m.weibo.cn/p/','').replace('100505','107603')
elif'https://m.weibo.cn/u/' in start_url:
containerid = '107603' +start_url.replace('https://m.weibo.cn/u/', '')
if containerid:
origin_url ='%s%s'%(url_head,containerid)
这里获取的origin_url就是需要的我们最终需要的获取微博内容的API路径。获取到API之后就是如何解析通过API返回的JSON数据。Python自带有一个读写JSON数据的JSON函数。
JSON模块提供了一种很简单的方式来编码和解码JSON数据。其中两个主要的函数是json.dumps()和json.loads(),要比其他序列化函数库如pickle的接口少得多。
json.dumps将Python对象编码成JSON字符串:
import json
data = {
'name' : 'scrapy爬虫框架',
'count' : 2
}
json_str =json.dumps(data)
json.loads将已编码的JSON字符串解码为Python对象:
data =json.loads(json_str)
有了json.loads函数之后就可以开始编写解析返回的微博JSON格式的数据代码。
content =json.loads(response.body)
weibo_info =content.get('cards',[])
最后贴上完整代码(这块的完整代码在最后可能会进行调整,因为微博的API也是实时在变的):
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
import json
# 微博内容
class WeiBoWBSpider(scrapy.Spider):
name = 'weibo_wb_spider'
allowed_domains = ['weibo.com']
def __init__(self, task_id=None, object_urls=None, *args, **kwargs):
super(WeiBoWBSpider, self).__init__(*args, **kwargs)
self.start_urls = ['https://m.weibo.cn/p/1005052803301701',
'https://m.weibo.cn/u/2189067512']
def start_requests(self):
for start_url in self.start_urls:
containerid = ''
url_head = 'https://m.weibo.cn/api/container/getIndex?containerid='
if 'https://m.weibo.cn/p/' in start_url:
containerid = start_url.replace('https://m.weibo.cn/p/','').replace('100505','107603')
elif 'https://m.weibo.cn/u/' in start_url:
containerid = '107603' + start_url.replace('https://m.weibo.cn/u/', '')
if containerid:
origin_url = '%s%s'%(url_head,containerid)
yield Request(origin_url,callback=self.parse)
def parse(self, response):
content = json.loads(response.body)
weibo_info = content.get('data').get('cards',[])
for info in weibo_info:
if info.get('mblog') and info.get('mblog').get('text'):
title = (info['mblog']['text']).encode('utf8')
url = "https://m.weibo.cn/status/%s" % info["mblog"]["mid"]
time_str = info.get('mblog').get('created_at').encode('utf8')
picture_urls = ''
if info.get('mblog').get('page_info'):
if info.get('mblog').get('page_info').get('media_info'):
picture_urls = info.get('mblog').get('page_info').get('page_pic')['url']
if not picture_urls:
if info.get('mblog').get('pics'):
pics = map(lambda x:x.get('url'),info["mblog"]["pics"])
picture_urls = ','.join(pics)
print '======微博内容======'
print title
print url
print time_str
print picture_urls
打印出的运行结果(部分)包括微博标题、微博的内容地址、发布时间以及图片地址为:
======微博内容======
#杭州新忆#
【杭州旅游】秋已渐远,冬即将来,空气中缕缕的桂花香味慢慢散去,立冬后的杭州,别有一番韵味……这里有杭州实时旅游资讯,旅游攻略,还有更多杭州大小故事,杭州旅游指南,陪你度过在杭州旅游的每一天!
https://m.weibo.cn/status/4171385368567714
11-07
https://wx4.sinaimg.cn/orj360/6aa731f3ly1fl9a3kc6y2j20m80et75z.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fl9a3ls4xwj20m80ej41x.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fl9a3mkh5sj20m80eagoe.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fl9a3n6ziwj20m80eqabl.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fl9a3j6z99j20by0bydgp.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fl9a3nwwcij20m80erdh4.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fl9a3oxlpaj20go0b1mzu.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fl9a3pij35j20dw08wjs1.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fl9a3pyq5uj20m80fbn1v.jpg
======微博内容======#晚安#好多好多美好的事情,就应该遇见,而不是追逐,或者等待。
https://m.weibo.cn/status/4184615768518710
2小时前
https://wx3.sinaimg.cn/orj360/6aa731f3ly1fmfhlea736j20c80iejt2.jpg
======微博内容======#深夜发吃#【吐司奶布丁】口感软嫩滑爽,很适合宝宝食用。制作中要用中火,火候不宜过大,否则容易蒸老,影响口感。
https://m.weibo.cn/status/4184585657322759
4小时前
https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfe4pozlmj20e60c2n0l.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfe4q6h5yj20e60c2jtd.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfe4qvxgpj20e60c2mz2.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfe4rcv90j20e60c2dho.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmfe4rnuvzj20e60c2dhs.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmfe4so4tfj20e60c2di7.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfe4tqbfuj20e60c2gnu.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfe4u23f3j20e60c2di5.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfe4wlw8yj20e60c2q6d.jpg
======微博内容======#世界真奇妙#芝加哥的冬天
https://m.weibo.cn/status/4184555507799312
6小时前
https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfant8vprj20j60ny125.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfao2st73j20j60nydok.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfao3fjjdj20j60ny125.jpg,https://wx3.sinaimg.cn/orj360/6aa731f3ly1fmfao62k8rj20j60ny12e.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmfao6iy2nj20j60nyqed.jpg,https://wx3.sinaimg.cn/orj360/6aa731f3ly1fmfao7lq4ij20j60nytjr.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmfao83bhrj20j60nyaiw.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmfao8k8ccj20j60nyakv.jpg,https://wx3.sinaimg.cn/orj360/6aa731f3ly1fmfao8v014j20j60nygsi.jpg
======微博内容======#乐活杭州#【收藏!杭州超适合拍照的场地攻略】拍照老司机总结的这几年在杭州拍照的场地,满满的干货,简直朋友圈乐活指南!快约起小伙伴拍照去吧~via.@林走心
https://m.weibo.cn/status/4184525577386862
8小时前
https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmf78dzspqj20j62umwpq.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmf78earamj20j62lrdpb.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmf78ekinhj20j62lr49a.jpg,https://wx3.sinaimg.cn/orj360/6aa731f3ly1fmf78f23uaj20j62tan90.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmf78fmu1aj20j6365n9v.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmf78g6q8nj20j62er7f7.jpg,https://wx1.sinaimg.cn/orj360/6aa731f3ly1fmf78gnofmj20j63m6dv4.jpg,https://wx2.sinaimg.cn/orj360/6aa731f3ly1fmf78h0dplj20j62lr7hd.jpg,https://wx4.sinaimg.cn/orj360/6aa731f3ly1fmf78hf67lj20j63t7nds.jpg
======微博内容======
转发微博
https://m.weibo.cn/status/4184503343729236
9小时前
======微博内容======
转发微博
https://m.weibo.cn/status/4184503226504812
9小时前
五、定义ITEM和通过Pipeline保存数据到MySQL数据库
我在简书之前已经讲解过ITEM,本文不再重复讲解(如果需要请留言),直接贴上定义的ITEM代码:
# encoding=utf-8
from scrapy.item import Item, Field
# 微博内容
class WeiBoListItem(Item):
# 微博标题
title = Field()
# 微博内容地址
url = Field()
# 微博的发布时间
time_str = Field()
# 微博带的图片地址
picture_urls = Field()
同样的,通过Pipeline保存数据到Mysql数据库也直接贴上对应的代码:
# -*- coding: utf-8 -*-
import MySQLdb
import MySQLdb.cursors
# 设置字符集,防止编码参数出错
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
from scrapy.exporters import JsonItemExporter
# 提交数据到mysql
class DataSubmitMysqlPipeline(object):
def __init__(self):
# 填写数据库用户名、数据库名、数据库用户密码、数据库url
self.conn = MySQLdb.connect(user='root', db='spider_db', passwd='root',
host='127.0.0.1',charset="utf8", use_unicode=True)
def process_item(self, item, spider):
insert_sql = """
insert into tb_weibo(title, url, time_str, picture_urls)
values(%s,%s,%s,%s)
"""
到此就完成了整个爬取微博内容的爬虫代码。
六、总结
通过ajax分析法找出获取请求数据的API是最高效的爬虫方式,然而这种方式也不是每个平台都像微博这块的这样顺利,很多网站也会对这些API设置反爬策略。常见的反爬策略包括如下几点:
1.cookie校验;
2.referer校验;
3.url中的参数校验。
当然对于这种反爬的反反爬策略肯定也是有的,将在后面Scrapy突破反爬的限制进行详细讲解。
如果对你有用,欢迎关注和转发。
领取专属 10元无门槛券
私享最新 技术干货