微信公众号爬虫

微信公众号爬虫

** 最近在翻看整理之前的工作代码和文档,发现2017年做的微信公众号爬虫的两张流程图,来聊聊曾经使用过的微信公众号爬虫方案吧。**

当时为了实现能够准实时的抓取微信公众号文章,先后实现了7、8种方案。每个方案都在使用一段时间之后,由于微信更新了反爬策略,使得对应的方案被废弃或者需要更新。

需求

这个东西的需求其实很简单

  • 获取指定公众号的文章列表(包括历史文章)
  • 需要获得文章的永久链接
  • 有新文章更新的话,抓取延迟在30分钟之内

问题

面临的问题主要有这么几个:

  • 微信的反爬是基于账号来做的,服务端那边有实时统计也有阶段性统计,一旦发现有爬取迹象,微信会直接封锁账号
  • 微信的服务端通信是加密的,不仅仅是 https,大部分重要的请求和返回的数据本身也是加密过的。好在公众号文章列表和内容都是 json 和 html 非加密格式的。
  • 起初抓取到的公众号链接都是永久链接,但是从2017年下半年某个时间开始,能够抓取到的公众号链接就只有临时链接了,临时链接有效时间大概是几个小时。过期之后,普通浏览器包括微信 Web 版本打开都会提示页面过期,只能通过微信原生客户端内置的浏览器打开。

方案

曾经采用过的方案大概包括

  • 利用搜狗的微信搜索或者文章列表和更新信息,利用搜狗的漏洞建立 cookie 池应对反爬
  • 利用中间人的方式,拦截微信客户端请求结果,抓取文章列表和文章链接
  • 利用微信 Android 客户端调起微信内部UI打开公众号详情页获得文章列表(通过逆向工程获得的公众号详情页调起方式)
  • 利用微信 Android 客户端调起微信内部浏览器打开文章临时链接,再利用中间人的方式获得永久链接
  • 从第三方抓取

我找到的这两张图应该是倒数第2和第3个方案,为了保证每个部件的灵活性导致整个调度流程很复杂(毕竟微信三天两头都在更新反爬策略)。最终的实现大概能够达到的效果是,跟踪10000多个公众号的更新,每天抓取大约3w 篇文章,更新延迟大概在10-30分钟左右。

不过放上来的两个方案现在只能是当做回忆,基本不可用了。所以,想做这个东西的同学就不必浪费时间尝试了。

这两个流程图真是看看都头大,这里面使用了 redis、flask api、mitm、微信 Android 客户端等各种。

方案1

方案2

再次强调一下,这两个方案现在由于包括微信反爬策略的调整等某些原因基本属于不可用的状态了。最新的可用方案就不放上来了,用的人越多被封锁的越快。

心得

现在的工作基本不碰爬虫了,讲那段时间研究微信公众号爬虫的一点点心得吧。不一定对,仅供参考。

微信的数据交互有两种方式,一种是 https 请求,一种是 wss 连接。微信的 web 版只用 https 请求方式(所以功能有限),原生微信客户端两种方式都用。

前者 https 请求可以通过中间人的方式拿到一部分请求的响应数据,但是大部分的请求和响应结果都是加密过的。

后者 wss 连接是在微信一启动的时候就会建立,这个wss 连接的数据交有一套协议,其中大概会交换加密密钥(猜测),这个加密密钥应该不是在客户端而是通过 wss 由服务端下发的,需要加密的 https 请求会使用 wss 那里拿到的加密密钥进行加密解密。

不过 wss 的通信协议程已经有人逆向出来了,现在很多微信机器人的高级功能都是通过这个方式实现的(简单的机器人可以直接用微信的 web api,但是复杂的就不行了),卖的不便宜哦。

Facebook的Dalvik运行期补丁

对于一个功能丰富的Android应用来说,要面对的挑战很多,其中很多问题绝大多数开发者都可能意识不到。Android应用的方法数量限制可能就是一个。

在一些传统的企业应用中,当处理跟业务数据相关的时候,通常不会将数据变量设置为公开访问public,而是会用getter/setter这样的存取方法来封装访问数据模型中的私有变量。在一般的编程实践中,这是一个被推荐和鼓励的做法。但是其副作用是类文件中会产生大量的方法(每一个私有变量都对应着2个方法)。

这个副作用带来的问题是,当应用安装在一些老机型上的时候,可能导致Android的一个Bughttp://code.google.com/p/android/issues/detail?id=22586。原因在于,在应用的安装过程中,会运行一个程序(dexopt)去根据当前机型为应用做一些特定的准备和优化。dexopt中使用了一个固定大小的缓冲区(名为LinearAlloc)来存储应用中方法的信息。最新的几个Android版本中,该缓冲的大小是8MB或16MB。但是Froyo(2.2)和Gingerbread(2.3)只有5MB。因为老版Android的这个限制,当方法数量超过这个缓冲大小的时候,会导致应用崩溃。

解决办法之一是可以利用http://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html提到的技术,将dex分成多个dex文件分别加载,首先加载核心dex,其他的模块和扩展功能放在其他的dex文件中。

但是某些时候,如果第二个dex包中的类需要被Android
Framework直接访问到的话,上述方案就是不可行的。此时必须要将第二个dex文件注入到Android的系统class loader中。这个一般情况下是无法做到的,但好在Android是开源的系统,可以利用Java的反射去修改Android内部的一些数据结构来解决这个问题。

这个方案在实际运行中,你会发现其实LinearAlloc不仅仅只是在dexopt中存在,而是在每个运行中的Android程序中都有它的身影。dexopt使用LinearAlloc储存dex文件的方法信息的时候,运行期的Android应用使用它访问实际用到的方法。如果在运行期将所有的dex文件都加载到一个进程中的话,实际的方法数量还是会超过限制。应用虽然可以启动但是很快就会崩溃。

看起来似乎要么只能精简应用的功能,要么就只能放弃支持Android部分版本而只支持最新的ICS以上的版本。

再次回头去看Android的源码,找到LinearAlloc缓冲的定义https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/LinearAlloc.h#L33,也许你能意识到:其实只要能将缓冲从5MB增加到8MB就可以安全运转了。

也许,可以使用JNI把当前的缓冲替换成一个更大的缓冲区。这个方案看起来太疯狂了。修改Java的Class Loader的内部是一回事儿,修改运行中的Davlik虚拟机的内部可是另外一回事儿——运行中的代码可能会很危险。但是仔细查看过代码、分析所有使用LinearAlloc的地方后发现,只要在应用启动的时候去做,应该没什么危险。所需要做的就是,找到LinearAlloc对象,锁定,然后替换缓冲。

事实上,只要找到它,后面的事情就很顺利了。在这里https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/Globals.h#L519,DvmGlobals对象中保存了LinearAlloc缓冲区。大概距离对象的开始地址700个字节的位置。从对象的开头开始扫描到这里风险很大,但是幸运的是,可以用距离一步之遥的vmList对象作为起点。这里包含了一个值,可以通过JNI和JavaVM的指针做比较。

最终方案是,找到vmList的值,扫描DvmGlobals对象找到匹配的位置,往后跳几个字节找到LinearAlloc的头,然后替换缓冲区。编写JNI扩展,嵌入到应用中,启动,然后应用应该可以正常运行在Gingerbread上了。

但是又有一个问题是,这个方案在Sumsung Galaxy S II上会失败,这可是最流行的运行Gingerbread的手机。

似乎三星对Android做了一些改动,导致这个方案的失败。其它的厂商可能会做同样的事情。所以这个方案的代码应该得更加健壮才行。

经过观察,在GS II上,LinearAlloc的缓冲区地址距离实际要找的地址只有4个字节。于是调整代码,如果在期望的地址没有找到LinearAlloc,那么就在附近几个字节的范围内查找。做这件事需要获取当前线程的内存映射表,确保没有在附近的查找过程中访问到无效地址(否则会立刻导致应用崩溃)。

(以上就是Facebook的故事,via

3rd-party apt-key list for Ubuntu

## Google Chrome

wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install google-chrome-stable

## JDK 1.7

echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
apt-get update
apt-get install oracle-java7-installer

## NodeJS

sudo add-apt-repository ppa:chris-lea/node.js

## MongoDB

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list

Failed to clone a large git repository: The remote end hung up unexpectedly

When git clone a large repository sometimes it give you the error message like this:

$ git clone ssh://xxx@xxx:29418/xxx
Cloning into 'xxx'...
remote: Counting objects: 356213, done
remote: Finding sources: 100% (356213/356213)
Corrupted MAC on input. (277847/356213), 123.59 MiB | 3.86 MiB/s
Finished discarding for xxx
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

If you got this error message, you might want to try run the following command to fix it

git config --global http.postBuffer 524288000
git config --global --add core.compression -1

genymotion Qt error in Ubuntu

When I was trying to install Genymotion on Ubuntu 14.04, I got this error

Installing log handler
Logging activities to file: /home/****
Aborted (core dumped)

the log file /home/xx/.Genymotion/genymotion.log has the following message:

... [Genymotion] [Fatal] Cannot mix incompatible Qt library (version 0x40806) with this library (version 0x40804)

The reason of this error is that Genymotion has its own Qt library which is not compatable with the system Qt library.

To fix this error, we need to let genymotion use system's Qt library. The following commands can fix this issue

sudo apt-get install libxi-dev libxmu-dev
# go to your genymotion directory
mkdir QtLibs && mv *Qt*.so* QtLibs

阅读笔记 2014-05-06

* [Pyston: 基于JIT的Python实现](https://github.com/dropbox/pyston)
* [React: Facebook面向的WebComponent的JavaScript库](http://facebook.github.io/react/index.html)
* [AngularJS or Ember - Ghost后台管理所采用的框架选择的讨论](https://github.com/TryGhost/Ghost/issues/2144)

可自动安装依赖的Ubuntu离线包安装工具 gdebi

# 可自动安装依赖的Ubuntu离线包安装工具 gdebi

Ubuntu下,通用的在线包管理工具是```apt```,但是对于下载好的离线deb包,我们通常会用```dpkg -i xxx.dev```来安装,但是这样常常会遇到依赖包不存在而无法安装的错误。dpkg不会为我们自动解决包依赖的问题,也不会自动下载安装所依赖的包。 gdebi是一个类似的管理工具,但是会自动下载安装依赖。

对于一个离线包,只需要使用下面的命令即可自动下载安装,包括所有依赖包

gdebi package_name.deb

可以使用下面这条命令安装这个工具

sudo apt-get install gdebi

better-package-tool-gdebi