要实现这个功能,首先要知道,微博的 mid 和 id 分别是什么?
在公众号以前的一篇文章 微博爬虫综述、错误汇总、Q&A 中,阐述了微博爬虫的不同目标站点之间的差异,并明确了我的微博爬虫的站点策略。
- 在 weibo.cn 站点爬取指定话题的微博,爬虫文件名是 WeiboTopicScrapy.py 。
- 在 m.weibo.cn 站点指定微博的评论,爬虫文件名是 WeiboSuperCommentScrapy.py。
在这两个站点,就算是同一个用户的同一条微博,其唯一标识也不一样,话题爬虫微博的是诸如 Is0XboARR
这样的形式,看上去是不规则的字符串,通常长度为 9,称之为微博的 mid,而后者是 4467107636950632
这样的数字形式(Is0XboARR
和 4467107636950632
指向同一条微博)。这样就产生了一个问题,如果我们想要爬取一个话题下的所有微博及其评论,难道要在 weibo.cn 爬完微博后,在 m.weibo.cn 搜每一条微博的文本以定位到该微博在 m.weibo.cn 的 id 吗?这样劳神劳力,完全不符合自动化工作的要求。也许你会问,为什么直接在 weibo.cn 爬取评论呢?问得好:weibo.cn 只能抓取前 100 页 差不多 1000 条评论文本,而 m.weibo.cn 可以抓取几千到几万条评论文本,因噎废食,那可不行。
那怎么办?作为一个程序猿,下意识地想到,mid 和 id 肯定存在某种关联,事实上刚好如此。以Is0XboARR
和 4467107636950632
为例,讲诉这种转化逻辑。
- 将
Is0XboARR
从 后 往 前 按四个字符为一组分组,即I
、s0Xb
、oARR
。 - 将这三组字符(串)转成对应的 62 进制的数字,从前往后拼接起来,就得到对应的数字 id 了。
该部分代码如下:
代码语言:javascript复制import execjs
jspython = '''str62keys = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
function int10to62(int10) {
var s62 = '';
var r = 0;
while (int10 != 0) {
r = int10 % 62;
s62 = this.str62keys.charAt(r) s62;
int10 = Math.floor(int10 / 62);
}
return s62;
}
function str62to10(str62) {
var i10 = 0;
for (var i = 0; i < str62.length; i ) {
var n = str62.length - i - 1;
var s = str62.substr(i, 1); // str62[i]; 字符串用数组方式获取,IE下不支持为“undefined”
i10 = parseInt(str62keys.indexOf(s)) * Math.pow(62, n);
}
return i10;
}
function id2mid(id) {
if (typeof (id) != 'string') {
return false; // id数值较大,必须为字符串!
}
var mid = '';
for (var i = id.length - 7; i > -7; i = i - 7)
{
var offset1 = i < 0 ? 0 : i;
var offset2 = i 7;
var num = id.substring(offset1, offset2);
num = int10to62(num);
mid = num mid;
}
return mid;
}
function mid2id(mid) {
var id = '';
for (var i = mid.length - 4; i > -4; i = i - 4) //从最后往前以4字节为一组读取mid字符
{
var offset1 = i < 0 ? 0 : i;
var len = i < 0 ? parseInt(mid.length % 4) : 4;
var str = mid.substr(offset1, len);
str = str62to10(str).toString();
if (offset1 > 0)
{
while (str.length < 7) {
str = '0' str;
}
}
id = str id;
}
return id;
}'''
ctx = execjs.compile(jspython) # 编译 js
mid = 'Is0XboARR'
id = ctx.call('mid2id', mid)
print(id)
但是,编写代码转化后,我惊讶地发现Is0XboARR
转化成的是4466768535861595
,而不是4467107636950632
`,在可以确保代码无误的情况下,我试了 N 个 M 次,没有一组匹配上,一首凉凉送给自己。
在 m.weibo.cn 站点上,一条微博评论的起始 URL 如下:
https://m.weibo.cn/comments/hotflow?id=4467107636950632&mid=4467107636950632
当时吧,就抱着试一试的心态,在浏览器输入了
https://m.weibo.cn/comments/hotflow?id=4466768535861595&mid=4466768535861595
没想到,居然出现了想要的微博的评论,真是山重水复疑无路,柳暗花明又一村。
经过大量测试,我发现这上面两条 URL,内容并不是完全相同,两者评论文本有不同程度的时延,有时前者领先 后者几分钟,有时后者领先前者几分钟,不过我觉着问题不大。猜想是微博评论的一个数据备份同步策略:真实的评论保存在某个未知的数据库中,依次同步到不同的站点,所以有时间差(当然只是猜想,具体还得问内部工作人员…)。
于是,在爬取一个话题的所有微博后,我们可以将那些评论数大于 0 的微博的 mid 批量转成 id,然后新建一个过渡 csv 文件,除了 mid、id 列,新增一个字段 isFinished 用来记录哪些微博的评论已经爬取过,方便出错了可以下次直接从没有爬取评论的第一条微博开始。
但是这样必须要解决的一个问题是,必须要知道什么时候一条微博的评论全部抓完,在上个版本的 WeiboSuperCommentScrapy.py 中,程序并没有自动判断什么时候停止?其实很简单,假如评论有 100 页,组装参数 101 页爬取后都是重复的评论,爬到重复的就应该停止了,所以每次爬取一条微博的所有评论时,如果列表中不存在评论的唯一标识 wid,就将 wid 追加 保存到列表中,否则就算该条微博的评论爬取结束.。