这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记
摘要
我的一次学以致用的经历,用在青训营里面学到知识,实实在在的帮助到同学,我觉得很开心,这使我认识到我们学的东西是非常有意义的,并且能够清晰的感觉到自己存在的价值。这种被需要的感觉还挺好的。我想把它记录下来。
背景介绍
第一节课上王老师给我们讲了一个简单在线词典程序,教我们如何分析网页请求,然后拿到结果进行处理,以达到我们的预期的目的,里面有两个工具还挺好用的,当时就随手放到收藏夹吃灰了。没想到会有翻出来的一天。
昨天恰好有个同学call我,ta说ta有个任务需要完成,就是在一个系统上进行审批,但是大概有个条目需要审批。ta进行这样的重复劳动好累,问我可以不可以帮ta完成(内心os:当然不可以了,一个一个点我也会好累)。所以我的回答是。但是也许可以偷偷懒,写个程序解决。
需求分析
听完ta的描述,我大概知道这应该是一个可以用程序解决的问题。我的脑海想到了两个方案:
- 用手机中比如说按键精灵之类的软件,循环模拟点击。
- 在浏览器里模拟点击也行。
但是这个看起来不够优雅,关键点来了,就在这个时候我想起了课上学的知识,突然就涌入我的脑海。我迅速打开浏览器,登录系统,开启检查,找到network分析了一下这个审批过程,原来每一次点击审批,都是对服务器发起的一个post请求,就是每次的参数都不一样。我内心一想!这。。。这不是和课上的简单在线词典程序差不多吗?
概要设计
我初步想了一下这个程序的流程应该怎么写
- 模拟登录系统
- 爬取网页内容
- 筛选需要的参数
- 对每一个参数发起请求
第一步很好实现,因为ta把账号密码告诉我了,我直接拿到了
cookie,然后登录即可
第二步开始百度,哈哈哈哈哈,笑死,百度了一个用go爬网页的程序,然后改改改。书到用时方恨少,这个时候用到了第一个工具,到网页中找到获取网页内容的那个get请求,然后copy as cURL(bash)
拿到这个之后,到第一个工具的网址,ctrl C + V,就生成了我们想要的东西,不得不说确实非常的方便!!!
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://zhsz.csedu.gov.cn/archive/content?stuId=2191367&reportState=1&audit=1", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Connection", "keep-alive")
//本行是cookie信息,我已经替换
req.Header.Set("Cookie", "xxxx")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Referer", "https://zhsz.csedu.gov.cn/archive/audit?stuId=2191367&reportState=1")
req.Header.Set("Sec-Fetch-Dest", "iframe")
req.Header.Set("Sec-Fetch-Mode", "navigate")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Mobile Safari/537.36")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
req.Header.Set("sec-ch-ua-mobile", "?1")
req.Header.Set("sec-ch-ua-platform", `"Android"`)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
仔细观察后我才发现原来是有规律的,注意到这些urls是用stuId的不同来区分的,然后到系统里找到第一个和最后一个,一个循环可以搞定。(当时的我这一步想了好久,还以为要一个一个爬下来)拿到目标urls之后,对每一个url发起get请求即可。
第三步 开始获取目标参数,同样也是分析网页,发现每一次的审核其实就是对服务器的一次post请求,就是每次的参数不一样。所以我们就需要从第二步中拿到HTML进行筛选,拿到这个网页中所有的gid,实现这一步的方法有很多,可是我一个都不会,我裂开
xpath,selector,正则等等都可以完成我们的目的
那没办法,现学咯,咔咔咔,又是一个小时过去了,以我现在的视角来看,我应该几分钟就学完的,虽然磕磕碰碰,但所幸好像学到一点东西,还是能筛选出来的,就是有重复gid,凑合用吧
第四步 就是拿到gids之后,对每一个gid发起请求即可,还是同样的复用上面的步骤。
到第一个工具中生成如下代码,然后封装成一个Post函数
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`gid=52f5d0b0a4df4383aeb1fd413756a390&auditState=-1`)
req, err := http.NewRequest("POST", "https://zhsz.csedu.gov.cn/archive/doAuditOne", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
// 本行已经替换
req.Header.Set("Cookie", "xxxx")
req.Header.Set("Origin", "https://zhsz.csedu.gov.cn")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Referer", "https://zhsz.csedu.gov.cn/archive/content?stuId=2191360&reportState=1&audit=1")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Mobile Safari/537.36")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
req.Header.Set("sec-ch-ua-mobile", "?1")
req.Header.Set("sec-ch-ua-platform", `"Android"`)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
详细设计
func GetUrls(start, end int) []string
输入:开始stuid和结束stuid 闭区间[start, end]
输出:一个包含urls的字符串切片
func GetHtml(url string) string
输入:一个url字符串
输出:该地址的html文本
func GetGids(htmls string) []string
输入:一串html文本
输出:一个包含gids的字符串切片
func Post(gid string)
输入:字符串gid
输出:打印请求返回的响应,以此判断请求是成功还是失败。
编码
我的详细代码就不放了,写得太垃圾了,我不想放出来丢人现眼,就放一个main吧
func main(){
urls := GetUrls(2191405, 2191405)
for _, url := range urls{
// fmt.Println(url)
html := GetHtml(url)
// fmt.Print(html)
gids := GetGids(html)
for _, gid := range gids{
Post(gid)
// fmt.Println(gid)
}
}
}
总结
其实事后回顾这个过程,其实会了就非常之简单。不会就千难万难,但是我却这样磕磕碰碰才完成,说明我以前欠下了很多债,终究是要还的!所以这一次的经历给我的体会很深,活到老,学到老。