使用chromedp解决反爬虫问题

https://lailin.xyz/post/5431.html

前言

最近We川大上的教务处公告新闻已经很久没有更新了,想到可能是ip被封了,查了一下log,发现并不是,而是获取到的页面全变成了混淆过的js,下面放两个格式化的函数

  1. function _$Es(_$Cu) {
  2. _$Cu[14] = _$v9();
  3. _$Cu[_$yf(_$ox(), 16)] = _$Dn();
  4. var _$cR = _$CR();
  5. _$cR = _$iT();
  6. return _$DA();
  7. }
  8. function _$Dk(_$Cu) {
  9. var _$x5 = _$Dv();
  10. var _$x5 = _$EB();
  11. if (_$Ex()) {
  12. _$w9 = _$Dw();
  13. }
  14. _$Cu[_$yf(_$EJ(), 16)] = _$ED();
  15. _$Cu[_$yf(_$Ep(), 16)] = _$EP();
  16. _$w9 = _$EB();
  17. return _$Cu[_$yf(_$v9(), 16)];
  18. }
  19. function _$rK() {
  20. var _$aJ = _$c0(_$DN());
  21. _$aJ = _$BC(_$aJ, 2);
  22. var _$Ce = _$yr(_$qt());
  23. for (var _$Cu = 0; _$Cu < _$aJ[_$gX()]; _$Cu++) {
  24. _$aJ[_$Cu] = _$Ce + _$aJ[_$Cu];
  25. }
  26. return _$aJ;
  27. }

看着这一堆就头大,但是本着只要是浏览器能够渲染出来的页面爬虫就可以爬到的原则,一步一步的解决

分析

  1. 先使用postman发送了一下请求,发现返回了上面一堆乱码
  2. 复制了正常渲染页面request header重新发送请求,可以得到正常的页面。考虑两个可能一个是header有什么特殊的处理,一个是cookie上的问题。
  3. header其他内容不变,去掉cookie重新发送请求,再一次得到一堆乱码。问题定位成功,应该就是cookie的问题了
  4. 清空chrome的缓存,重新加载页面,查看请求记录,可以看到这个页面一共加载了两次
    第一次加载
    第一次加载没有返回cookie
    第二次加载
    第二次加载返回了一个JSESSIONID,这个应该就是最终需要的cookie了

  5. 观察两次请求的中间,我们可以发现还有两个请求,这两个请求应该就是第二次返回cookie的原因了,第一个请求是页面内的外链js文件,第二个请求应该就是混淆过的js发出的请求了。

  6. 因为实力有限,分析了几个小时都没有分析出来这个逻辑是怎么加载的。但是想到了直接从浏览器把cookie复制下来给爬虫使用不就可以了?但是这样也还有一个问题,就是不可能每一次都手动的去获取cookie这样达不到想要的效果。然后看到Python有使用Selenium来完全模拟浏览器渲染然后解析页面的爬虫案例,找了一下golang有没有类似的浏览器渲染方案,在万能的gayhub上找到了chromedp。下面使用chromedp来解决这个问题。

chromedp

Package chromedp is a faster, simpler way to drive browsers (Chrome, Edge, Safari, Android, etc) without external dependencies (ie, Selenium, PhantomJS, etc) using the Chrome Debugging Protocol.

1.install(建议使用梯子)
  1. go get -u github.com/chromedp/chromedp
2.code

运行下面这一段代码可以看到chrome会弹出一个窗口并且运行网页,最后在console输出期望的html,但是我们其实只需要得到正确的cookie,用来之后爬取网页使用。如果所有的页面都需要等待chrome渲染结束之后爬取,那么效率实在是太低了

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "time"
  8. "github.com/chromedp/cdproto/cdp"
  9. "github.com/chromedp/chromedp"
  10. )
  11. func main() {
  12. var err error
  13. // create context
  14. ctxt, cancel := context.WithCancel(context.Background())
  15. defer cancel()
  16. // create chrome instance
  17. c, err := chromedp.New(ctxt, chromedp.WithLog(log.Printf))
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. // run task list
  22. var res string
  23. err = c.Run(ctxt, chromedp.Tasks{
  24. // 访问教务处页面
  25. chromedp.Navigate(`http://jwc.scu.edu.cn/jwc/moreNotice.action`),
  26. // 等待table渲染成功,成功则说明已经获取到了正确的页面
  27. chromedp.WaitVisible(`table`, chromedp.ByQuery),
  28. // 获取body标签的html字符
  29. chromedp.OuterHTML("body", &res),
  30. })
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. // 关闭chrome实例
  35. err = c.Shutdown(ctxt)
  36. if err != nil {
  37. log.Fatal(err)
  38. }
  39. // 等待chrome实例关闭
  40. err = c.Wait()
  41. if err != nil {
  42. log.Fatal(err)
  43. }
  44. // 输出html字符串
  45. log.Printf(res)
  46. }

修改第2步当中task list 的代码获取cookie,修改之后可以看到console当中输出了一段cookie字符串,使用这个cookie在postman当中测试可以发现,可以获取到正确的页面。到了这一步其实就应该算基本完成了,但是还是有一个缺点:每次运行的时候都会弹出一个chrome窗口,爬虫在服务器上运行是没有gui页面的,并且每次打开一个chrome实例的时间开销也比较大。

  1. // 将chromedp.OuterHTML("body", &res) 替换为下面的代码
  2. chromedp.ActionFunc(func(ctx context.Context, h cdp.Executor) error {
  3. // 获取cookie
  4. cookies, err := network.GetAllCookies().Do(ctx, h)
  5. // 将cookie拼接成header请求中cookie字段的模式
  6. var c string
  7. for _, v := range cookies {
  8. c = c + v.Name + "=" + v.Value + ";"
  9. }
  10. log.Println(c)
  11. if err != nil {
  12. return err
  13. }
  14. return nil
  15. }),
5.使用chrome的headless模式

a.使用docker运行一个headless模式的chrome

  1. docker run -d -p 9222:9222 --rm --name chrome-headless knqz/chrome-headless

b.修改代码

可以看到主要的区别就在创建chrome实例的时候没有去启动一个chrome,当然最后也不需要去关闭它

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "github.com/chromedp/chromedp/client"
  6. "github.com/chromedp/cdproto/network"
  7. "github.com/chromedp/cdproto/cdp"
  8. "github.com/chromedp/chromedp"
  9. )
  10. func main() {
  11. var err error
  12. // create context
  13. ctxt, cancel := context.WithCancel(context.Background())
  14. defer cancel()
  15. // create chrome instance
  16. c, err := chromedp.New(ctxt, chromedp.WithTargets(client.New().WatchPageTargets(ctxt)), chromedp.WithLog(log.Printf))
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. // run task list
  21. err = c.Run(ctxt, chromedp.Tasks{
  22. // 访问教务处页面
  23. chromedp.Navigate(`http://jwc.scu.edu.cn/jwc/moreNotice.action`),
  24. // 等待table渲染成功,成功则说明已经获取到了正确的页面
  25. chromedp.WaitVisible(`table`, chromedp.ByQuery),
  26. // 获取body标签的html字符
  27. chromedp.ActionFunc(func(ctx context.Context, h cdp.Executor) error {
  28. // 获取cookie
  29. cookies, err := network.GetAllCookies().Do(ctx, h)
  30. // 将cookie拼接成header请求中cookie字段的模式
  31. var c string
  32. for _, v := range cookies {
  33. c = c + v.Name + "=" + v.Value + ";"
  34. }
  35. log.Println(c)
  36. if err != nil {
  37. return err
  38. }
  39. return nil
  40. }),
  41. })
  42. if err != nil {
  43. log.Fatal(err)
  44. }
  45. }

到这里基本就可以使用了,获取到cookie之后可以使用喜欢的方式去获取页面

推荐阅读

ft_authoradmin  ft_create_time2019-05-27 14:47
 ft_update_time2019-05-27 14:48