我在知乎发了一篇文章,讲述我是如何根治我的神经性皮炎
p0h5:[06月10日更新]2年神经性皮炎的痊愈历程(已根治剩余药膏免费送)多图预警zhuanlan.zhihu.com同时把没用完的药免赠送
但是知乎不能发联系方式,于是很多知乎的病友给我发私信
因为工作的原因,公司内网不方便上知乎
手机也用得少
很多时候,知乎病友发来的消息,要很久才能回复,造成了沟通问题
于是考虑能不能自动回复知乎的私信,让病友通过微信交流
分析一波网页请求先
可以看到,知乎采用的是后端渲染,我们可以直接抓html的内容
首选,我们构造一下请求
这里选用了ghttp这个库
github.com/gogf/gf/g/net/ghttp
package main
import (
"github.com/gogf/gf/g/net/ghttp"
var req *ghttp.Client
func init() {
req=ghttp.NewClient()
}
func main() {
req.SetHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36")
req.SetHeader("referer", "https://www.zhihu.com/inbox")
req.SetHeader("upgrade-insecure-requests","1")
req.SetHeader("Pragma","no-cache")
res, _ :=req.Get("https://www.zhihu.com/inbox")
defer res.Close()
fmt.Println(res.ReadAllString())
}
构造一个client,并初始化,加入响应,可以看到,成功响应,但是这是未登陆状态,那么还有一个关键,就是cookie
<!doctype html>
<html lang="zh" data-hairline="true" data-theme="light">
<head>
<meta charSet="utf-8"/>
<title data-react-helmet="true">知乎 - 有问题,上知乎</title>
<meta nport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
<meta name="renderer" content="webkit"/><meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="google-site-verification" content="FTeR0c8arOPKh8c5DYh_9uu98_zJbaWw53J-Sch9MTg"/>
<meta name="description" property="og:description" content="有问题,上知乎xxxx"/>
<link rel="shortcut icon" type="image/x-icon" href="https://static.zhihu.com/static/favicon.ico"/>
<link rel="search" type="aensearchdescription+xml" href="https://static.zhihu.com/static/search.xml" title="知乎"/>
现在加入cookie,我们从浏览器拿到的cookie是字符串,需要构造成map
func cookiestringtomap(str string) map[string]string {
ck:=make(map[string]string)
str=strings.ReplaceAll(str," ","")
items:=strings.Split(str,";")
for _, item := range items {
v:=strings.Split(item,"=")
ck[v[0]]=v[1]
}
return ck
}
带cookie请求的代码如下
package main
import (
"fmt"
"github.com/gogf/gf/g/net/ghttp"
"strings"
)
var req *ghttp.Client
const COOKIE = `从网页复制的cookie`
func init() {
req=ghttp.NewClient()
}
func main() {
req.SetHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36")
req.SetHeader("referer", "https://www.zhihu.com/inbox")
req.SetHeader("upgrade-insecure-requests","1")
req.SetHeader("Pragma","no-cache")
req.SetCookieMap(cookiestringtomap(COOKIE))
res, _ :=req.Get("https://www.zhihu.com/inbox")
defer res.Close()
fmt.Println(res.ReadAllString())
}
func cookiestringtomap(str string) map[string]string {
ck:=make(map[string]string)
str=strings.ReplaceAll(str," ","")
items:=strings.Split(str,";")
for _, item := range items {
v:=strings.Split(item,"=")
ck[v[0]]=v[1]
}
return ck
}
请求结果如下,可以发现,登陆状态已经正常:
<a href="/people/p0h5">
<i class="zg-icon zg-icon-dd-home"></i>我的主页
</a>
</li>
<li>
<a href="/inbox">
<i class="zg-icon zg-icon-dd-pm"></i>私信
<span id="zh-top-nav-pm-count" class="zu-top-nav-pm-count zg-noti-number"
style="visibility:hidden" data-count="0">
</span>
</a>
现在开始发送私信,并抓包,写发私信的方法
可以看到,发送私信,需要message_id,content,type三个参数,我们来找一找
通过分析网页发现
message_id,type都有了,content就是内容
开始构造发消息方法
func SendMsg(msgid string,content string) int {
req.SetHeader("x-requested-with","XMLHttpRequest")
res,err:=req.Post("https://www.zhihu.com/inbox/reply",g.Map{"message_id":msgid,"content":content,"type":"common"})
if err!=nil{
log.Println(err.Error())
return 0
}
defer res.Close()
return res.StatusCode
}
开始运行,走你
SendMsg("1123902504945291264","测试测试")
得到返回
<html><title>403: Forbidden</title><body><h1>403: Forbidden</h1></body></html>
什么鬼,403???不允许我发?
仔细在查看网页请求
原来还有一个x-xsrftoken的请求头,参数从哪儿来的呢?
发现有两个地方可以拿到token
一个是cookie,一个是网页中
我决定从网页中拿,同时把最后回复的那个id拿到
这里用到了goquery这个库,来取网页信息
func GetLastIdandToken() (err error,lastid string,token string) {
res,err:=req.Get("https://www.zhihu.com/inbox")
if err!=nil{
return err,"",""
}
defer res.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
return err,"",""
}
lastid = doc.Find("#zh-pm-item-wrap").Find(".zm-pm-item").AttrOr("data-token","")
token = doc.Find("input[name='_xsrf']").AttrOr("value","")
return
}
ok ,拿到最新的ID和token
接下来在main函数获取启动参数,cookie和最新的消息id
if len(os.Args)<3{
log.Println("启动参数为 ./xxx cookie lastid")
os.Exit(0)
}
argcook:=os.Args[1]
arglastid:=os.Args[2]
再构建一个循环,来不停处理消息
最终代码如下:
package main
import (
"github.com/PuerkitoBio/goquery"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"log"
"math/rand"
"os"
"strings"
"time"
)
var req *ghttp.Client
func init() {
req=ghttp.NewClient()
}
func main() {
if len(os.Args)<3{
log.Println("启动参数为 ./xxx cookie lastid")
os.Exit(0)
}
argcook:=os.Args[1]
arglastid:=os.Args[2]
req.SetHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36")
req.SetHeader("referer", "https://www.zhihu.com/inbox")
req.SetHeader("upgrade-insecure-requests","1")
req.SetHeader("Pragma","no-cache")
req.SetCookieMap(cookiestringtomap(argcook))
req.BrowserMode(true)
for {
err,lasid,token:=GetLastIdandToken()
if err!=nil {
log.Println(err.Error())
break
}
if lasid!=arglastid {
log.Println("find new msgid,need reply")
time.Sleep(2 *time.Second)
code:=SendMsg(lasid,"你好,xxxxxx" ,token)
if code==200{
time.Sleep(2 *time.Second)
err,lasid,token=GetLastIdandToken()
if err!=nil {
log.Println(err.Error())
break
}
if lasid!="" && lasid!=arglastid {
log.Println("send msg success!","set last id",lasid)
arglastid=lasid
}
}else {
log.Println("send msg error!",code)
}
}
x:=Getrandom(20)
//log.Println("等待",x,"秒后继续刷新")
time.Sleep(time.Second * time.Duration(x))
}
}
func cookiestringtomap(str string) map[string]string {
ck:=make(map[string]string)
str=strings.ReplaceAll(str," ","")
items:=strings.Split(str,";")
for _, item := range items {
v:=strings.Split(item,"=")
ck[v[0]]=v[1]
}
return ck
}
func GetLastIdandToken() (err error,lastid string,token string) {
//log.Println("开始请求....")
res,err:=req.Get("https://www.zhihu.com/inbox")
if err!=nil{
return err,"",""
}
defer res.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
return err,"",""
}
lastid = doc.Find("#zh-pm-item-wrap").Find(".zm-pm-item").AttrOr("data-token","")
token = doc.Find("input[name='_xsrf']").AttrOr("value","")
//log.Println("lastid=",lastid," token=",token)
return
}
func SendMsg(msgid string,content string,token string) int {
//log.Println("开始发送消息...")
req.SetHeader("x-requested-with","XMLHttpRequest")
req.SetHeader("x-xsrftoken",token)
res,err:=req.Post("https://www.zhihu.com/inbox/reply",g.Map{"message_id":msgid,"content":content,"type":"common"})
if err!=nil{
log.Println(err.Error())
return 0
}
defer res.Close()
//log.Println(res.ReadAllString())
return res.StatusCode
}
func Getrandom(max int) int64 {
rand.Seed(time.Now().Unix())
return int64(rand.Intn(max))
}
编译,压缩,走起
完事上传服务器并启动
./zhihu.min cookiexxxxxxxxxxxxx 最新的消息id
服务器上跑起来OK
现在再次测试
现在的逻辑是,不断刷新消息列表,如果本次请求的最新id和上一次不一样,则给最新的消息发送一个消息,所以我自己发了消息,也会得到自动回复。
全文完,上面代码可以直接复制粘贴跑起来