Webhook接口开发

Webhook请求参数

Webhook以HTTP POST的方式发送数据到用户服务器,Content-Typeapplication/x-www-form-urlencoded; charset=UTF-8。根据通知事件不同,发送的数据也不同。

  • 事件类型是新增数据变动数据时,参数说明如下:
参数参数说明备注
url爬取或者监控的网页url
timestamp当前时间秒级时间戳
sign2url+user_secret+timestamp的md5值用来校验数据是否合法
data爬取或者监控到的数据JSON字符串
event_type事件类型
新增数据 data.new
变动数据 data.updated
crawl_time爬取到这条数据的时间秒级时间戳
data_key这条数据的唯一标识, 值是爬虫id+这条数据的id(爬虫爬取结果中一条数据的唯一标识)Webhook调用成功需要返回此值
pk数据版本的唯一标识
  • 事件类型是自定义数据时,参数说明如下:
参数参数说明备注
timestamp当前时间秒级时间戳
sign2user_secret+timestamp的md5值用来校验数据是否合法
msg调用system.sendWebhook函数发送的自定义数据 无内容时, 值为空字符串
event_type事件类型
自定义数据 msg.custom
data_key爬虫idWebhook调用成功需要返回此值

POST数据示例:

参数 参数值
url https://baijia.baidu.com/s?id=1588622154406128646
timestamp 1515073151
sign2 f245b6af55cf526b08c126f2cb0a497a
data {“article_title”: “xxx”, “article_author”: “xxx”, “article_content”: “xxx”, “article_publish_time”: “xxx”}
event_type data.new
crawl_time 1477498921
data_key 1000:136

Webhook数据压缩

对于单条数据内容比较多的,建议开启数据压缩,以节省网络流量,提高发送速度。开启压缩后,平台将会对数据进行gzip压缩,然后发送。
服务器端接收数据需要有相应的解压操作,可以配置Apache/Nginx进行解压,也可以使用具体的后端语言进行解压,参考如何压缩 HTTP 请求正文
下面是使用PHP语言进行解压的示例:

$encoding = $_SERVER['HTTP_CONTENT_ENCODING'];
if ($encoding == 'gzip') {
$body = gzdecode(file_get_contents('php://input'));
parse_str($body, $_POST);
}
// 后面就可以正常读取$_POST参数了

下面是使用Java语言进行解压的示例:

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String contentEncoding = request.getHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.toLowerCase().contains("gzip")) {
try {
InputStream in = request.getInputStream();
GZIPInputStream ungzip = new GZIPInputStream(in);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
in.close();
String dataStr = out.toString("utf-8");
Map<String, String> parameters = new HashMap<String, String>();
for (String str : dataStr.split("&")) {
String[] keyValue = str.split("=");
parameters.put(URLDecoder.decode(keyValue[0], "utf-8"), URLDecoder.decode(keyValue[1], "utf-8"));
}
// POST的参数在parameters变量中
} catch (Exception e) {
e.printStackTrace();
}
}
}

Webhook返回值

用户服务器成功接收并处理Webhook数据之后, 请返回data_key的值, 否则神箭手会认为此次Webhook调用失败, 会以前面描述的机制进行重试.

Webhook示例代码

开发者需要在自己的服务器上部署接收Webhook数据的代码。一般需要先校验数据的合法性,确保Webhook是神箭手发出的。

下面给出PHP和Java的代码示例(主要是校验部分,数据处理部分需要开发者根据自身需求去处理):

PHP示例代码:

// 兼容开启gzip数据压缩的情况
$encoding = $_SERVER['HTTP_CONTENT_ENCODING'];
if ($encoding == 'gzip') {//表示开启了gzip压缩
$body = gzdecode(file_get_contents('php://input'));//读取并解压数据
parse_str($body, $_POST);//将POST数据解析到全局变量$_POST中
}
// 此处需修改为你的神箭手账户的"user_secret"
// 神箭手控制台"用户中心"的"用户密钥"就是"user_secret"
$user_secret = 'your userSecret';
$sign2 = $_POST['sign2'];
$url = $_POST['url'];
$timestamp = $_POST['timestamp'];
if (md5($url . $user_secret . $timestamp) === $sign2) {
$data = $_POST['data'];
// 处理数据
...
// 最后, 你需要输出"data_key"
echo $_POST['data_key'];
} else {
// 安全校验未通过拒绝响应
}

Java示例代码:

// 此处需修改为你的神箭手账户的"user_secret"
// 神箭手控制台"用户中心"的"用户密钥"就是"user_secret"
private static final String USER_SECRET = "your userSecret";

@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp) throws ServletException {
String sign2, url, timestamp, data, dataKey;
// 兼容开启gzip数据压缩的情况
String contentEncoding = request.getHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.toLowerCase().contains("gzip")) {
InputStream in = request.getInputStream();
Map<String, String> parameters = uncompressToParameters(in, "utf-8");
in.close();
sign2 = parameters.get("sign2");
url = parameters.get("url");
timestamp = parameters.get("timestamp");
data = parameters.get("data");
dataKey = parameters.get("data_key");
}
else {
sign2 = req.getParameter("sign2");
url = req.getParameter("url");
timestamp = req.getParameter("timestamp");
data = req.getParameter("data");
dataKey = req.getParameter("data_key");
}
// 依赖"org.apache.commons"库
if (sign2.equals(DigestUtils.md5Hex(url + USER_SECRET + timestamp))) {
// 处理数据
...
// 最后, 你需要输出"data_key"
resp.getWriter().write(dataKey);
}
}

/**
* gzip解压RequestBody,并解析参数
* @param in Request inputStream
* @return 参数集合
*/
public static Map<String, String> uncompressToParameters(InputStream in) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
String dataStr = out.toString("utf-8");
Map<String, String> dataMap = new HashMap<>();
for (String str : dataStr.split("&")) {
String[] keyValue = str.split("=");
dataMap.put(URLDecoder.decode(keyValue[0], "utf-8"), URLDecoder.decode(keyValue[1], "utf-8"));
}
return dataMap;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

Python示例代码

#示例使用的是tornado框架,仅供参考
import logging
import hashlib

import tornado.ioloop
import tornado.web
from tornado import options

try:
from urllib.parse import unquote
except ImportError:
# Python 2.
from urllib import unquote


USER_SECRET = "user secret"

def generateMD5(str):
hl = hashlib.md5()
hl.update(str.encode(encoding='UTF-8'))
return hl.hexdigest()


class WebHookHandler(tornado.web.RequestHandler):
def post(self):
sign2 = self.get_body_argument("sign2")
url = self.get_body_argument("url")
timestamp = self.get_body_argument("timestamp")
data = self.get_body_argument("data")
data_key = self.get_body_argument("data_key")

if(generateMD5(url + USER_SECRET + timestamp) == sign2):
# 处理数据
# ...
# 最后, 你需要输出"data_key"
self.write(data_key)

class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/webhook", WebHookHandler),
]
settings = dict(
)
tornado.web.Application.__init__(self, handlers, **settings)

if __name__ == "__main__":
# Tornado configures logging.
options.parse_command_line()
app = Application()
#在tornado框架中使用decompress_request参数开启gzip支持
#其他的框架可以搜索 "接收Content-Encoding:zip" 相关方法...
http_server = tornado.httpserver.HTTPServer(app, decompress_request=True)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()