博客搬家系列(六)-爬取今日头条文章

一.前情回顾

博客搬家系列(一)-简介:https://blog.csdn.net/rico_zhou/article/details/83619152

博客搬家系列(二)-爬取CSDN博客:https://blog.csdn.net/rico_zhou/article/details/83619509

博客搬家系列(三)-爬取博客园博客:https://blog.csdn.net/rico_zhou/article/details/83619525

博客搬家系列(四)-爬取简书文章:https://blog.csdn.net/rico_zhou/article/details/83619538

博客搬家系列(五)-爬取开源中国博客:https://blog.csdn.net/rico_zhou/article/details/83619561

博客搬家系列(七)-本地WORD文档转HTML:https://blog.csdn.net/rico_zhou/article/details/83619573

博客搬家系列(八)-总结:https://blog.csdn.net/rico_zhou/article/details/83619599

二.开干(获取文章URL集合)

爬取今日头条的文章算是本系列中比较难的,不像其他如CSDN等网站,基本信息可以直接使用htmlunit就能爬取,但是当用同样的方法爬取今日头条时,则不行,很简单,我们随便找一多文章的博主,如https://www.toutiao.com/c/user/101528687217/ 打开主页,右击查看源码,我们发现源码中并不包含文章列表等信息,说明文章列表是js动态加载的,于是还是老规矩,先审查元素,查看一下都进行了哪些请求再说

发现这个get请求正是我想要的,preview查看一下不难发现这里的数据即是文章列表,但是我却并没有在url中发现跟页数相关的参数,只是滚动会发现另一个请求,而且最后的三个参数as,cp,_signature是不一样的,当max_behot_time=0时,即第一页信息,随便更改这三个参数均能得到正确数据

但是当不等于0的时候,即第2,3...页信息,则后面的三个参数就起了作用,不能随便了,其实想想也知道,这肯定是签名算法,防的就是我们现在的这个行为,哈哈,好吧,先说结果,目前我还没有找到有效的办法,但是可以将过程同大家分享一下,共同学习。

首先,这三个参数的加密算法是什么需要找到,然后才能破解,我们分析一下这条url

https://www.toutiao.com/c/user/article/?page_type=1&user_id=101528687217&max_behot_time=1539352612&count=20&as=A1851BCD7A2C7D8&cp=5BDADC373D48DE1&_signature=.MrxjBAapxqr.bnXWOFkBPzK8Z

前面的参数其实不用管如page_type,count,但是user_id和max_behot_time是变化的,user_id是已知的,那么max_behot_time是哪来的呢?

当我们首页信息时max_behot_time是0,下拉时变成了1539352612,这个过程中没有其他的请求发生,那么就是在第一次请求时返回的信息或者通过js计算的数据,首先检查一下首次返回的数据:

发现果然如此,这个参数我们是可以依次获取的,解决,那么as,cp,_signature这三个参数呢?刚刚分析,可能是js计算而来,那我们就把这几个关键字在所有的js文件中搜索一下,找到相关的js代码先

经搜索发现,我们找到了相关的js,即文件index_34154e5.js,将其下载下来分析,我们找到了as,cp这两个参数的算法

分析发现,这两个参数只是根据当前的时间经过md5加密生成的字符串,并没有用到user_id和max_behot_time参数,我们网上百度个md5算法或者引入md5js文件,简单写个html获取这两个参数试试看,

<html><head>
<title>页面</title>
</head><body>
<p>。</p>
<p>。</p>
</body>
<script>console.log(getHoney(0,1539352612));function getHoney(userInfoId,max_behot_time){var i = Math.floor((new Date).getTime() / 1e3), e = i.toString(16).toUpperCase(), t = md5(i).toString().toUpperCase();if (8 != e.length)return {as: "479BB4B7254C150",cp: "7E0AC8874BB0985"};for (var n = t.slice(0, 5), o = t.slice(-5), s = "", a = 0; 5 > a; a++)s += n[a] + e[a];for (var l = "", r = 0; 5 > r; r++)l += e[r + 3] + o[r];return {as: "A1" + s + e.slice(-3),cp: e.slice(0, 3) + l + "E1"}}function md5(string) {
....
}
</script>
</html>

经测试,这两个参数是可用的,那问题的关键就在于_signature这个参数了,同样搜索文件发现了算法

可以看出_signature这个值是需要参数user_id和max_behot_time作为参数经过方法TAC.sign签名之后获取的,那么现在就有两条路了

第一条路是破解这个_signature签名的算法,其实js中都有,我只要改成java实现,或者直接使用java执行js即可

第二条路是使用htmlunit动态的执行js方法TAC.sign()然后得到结果

先尝试第二种貌似比较简单的实现方法

可是将其带入到url中依然无法得到正确结果,可能毕竟运行有差别吧,不做多想,再试试方案一!

加密算法在这

显然含有特殊字符,暂时不管,先复制到本地html中试试看,经测试发现是可以用的,但由于特殊字符的原因不太好改成java版,偶然间在网上搜索竟然发现有人对这个算法研究了,结果也很失望,主要是大多是Python版的且多失效或者是使用selenium需要浏览器,并不是我想要的,不过倒是这篇管用一点,链接忘了,找到了会在尾部贴出。主要是将带有特殊字符的加密算法去除,重点如下:

function e(e, a, r) {return (b[e] || (b[e] = t("x,y", "return x " + e + " y")))(r, a)}function a(e, a, r) {return (k[r] || (k[r] = t("x,y", "return new x[y](" + Array(r + 1).join(",x[++y]").substr(1) + ")")))(e, a)}function r(e, a, r) {var n, t, s = {},b = s.d = r ? r.d + 1 : 0;for (s["$" + b] = s,t = 0; t < b; t++)s[n = "$" + t] = r[n];for (t = 0,b = s.length = a.length; t < b; t++)s[t] = a[t];return c(e, 0, s)}function c(t, b, k) {function u(e) {v[x++] = e}function f() {return g = t.charCodeAt(b++) - 32,t.substring(b, b += g)}function l() {try {y = c(t, b, k)} catch (e) {h = e,y = l}}for (var h, y, d, g, v = [], x = 0;;)switch (g = t.charCodeAt(b++) - 32) {case 1:u(!v[--x]);break;case 4:v[x++] = f();break;case 5:u(function (e) {var a = 0,r = e.length;return function () {var c = a < r;return c && u(e[a++]),c}}(v[--x]));break;case 6:y = v[--x],u(v[--x](y));break;case 8:if (g = t.charCodeAt(b++) - 32,l(),b += g,g = t.charCodeAt(b++) - 32,y === c)b += g;else if (y !== l)return y;break;case 9:v[x++] = c;break;case 10:u(s(v[--x]));break;case 11:y = v[--x],u(v[--x] + y);break;case 12:for (y = f(),d = [],g = 0; g < y.length; g++)d[g] = y.charCodeAt(g) ^ g + y.length;u(String.fromCharCode.apply(null, d));break;case 13:y = v[--x],h = delete v[--x][y];break;case 14:v[x++] = t.charCodeAt(b++) - 32;break;case 59:u((g = t.charCodeAt(b++) - 32) ? (y = x,v.slice(x -= g, y)) : []);break;case 61:u(v[--x][t.charCodeAt(b++) - 32]);break;case 62:g = v[--x],k[0] = 65599 * k[0] + k[1].charCodeAt(g) >>> 0;break;case 65:h = v[--x],y = v[--x],v[--x][y] = h;break;case 66:u(e(t.substr(b++, 1), v[--x], v[--x]));break;case 67:y = v[--x];d = v[--x];g = v[--x];u(g.x === c ? r(g.y, y, k) : g.apply(d, y));break;case 68:u(e((g = t.substr(b++, 1)) < "<" ? (b--,f()) : g + g, v[--x], v[--x]));break;case 70:u(!1);break;case 71:v[x++] = n;break;case 72:v[x++] = +f();break;case 73:u(parseInt(f(), 36));break;case 75:if (v[--x]) {b++;break}case 74:g = t.charCodeAt(b++) - 32 << 16 >> 16,b += g;break;case 76:u(k[t.charCodeAt(b++) - 32]);break;case 77:y = v[--x],u(v[--x][y]);break;case 78:g = t.charCodeAt(b++) - 32,u(a(v, x -= g + 1, g));break;case 79:g = t.charCodeAt(b++) - 32,u(k["$" + g]);break;case 81:h = v[--x],v[--x][f()] = h;break;case 82:u(v[--x][f()]);break;case 83:h = v[--x],k[t.charCodeAt(b++) - 32] = h;break;case 84:v[x++] = !0;break;case 85:v[x++] = void 0;break;case 86:u(v[x - 1]);break;case 88:h = v[--x],y = v[--x],v[x++] = h,v[x++] = y;break;case 89:u(function () {function e() {return r(e.y, arguments, k)}return e.y = f(),e.x = c,e}());break;case 90:v[x++] = null;break;case 91:v[x++] = h;break;case 93:h = v[--x];break;case 0:return v[--x];default:u((g << 16 >> 16) - 16)}}var n = window;var t = n.Function,s = Object.keys || function (e) {var a = {},r = 0;for (var c in e)a[r++] = c;return a.length = r,a},b = {},k = {};console.log(t('x','return x+1'))r(decodeURIComponent("gr%24Daten%20%D0%98b%2Fs!l%20y%CD%92y%C4%B9g%2C(lfi~ah%60%7Bmv%2C-n%7CjqewVxp%7Brvmmx%2C%26eff%7Fkx%5B!cs%22l%22.Pq%25widthl%22%40q%26heightl%22vr*getContextx%24%222d%5B!cs%23l%23%2C*%3B%3F%7Cu.%7Cuc%7Buq%24fontl%23vr(fillTextx%24%24%E9%BE%98%E0%B8%91%E0%B8%A0%EA%B2%BD2%3C%5B%23c%7Dl%232q*shadowBlurl%231q-shadowOffsetXl%23%24%24limeq%2BshadowColorl%23vr%23arcx88802%5B%25c%7Dl%23vr%26strokex%5B%20c%7Dl%22v%2C)%7DeOmyoZB%5Dmx%5B%20cs!0s%24l%24Pb%3Ck7l%20l!r%26lengthb%25%5El%241%2Bs%24j%02l%20%20s%23i%241ek1s%24gr%23tack4)zgr%23tac%24!%20%2B0o!%5B%23cj%3Fo%20%5D!l%24b%25s%22o%20%5D!l%22l%24b*b%5E0d%23%3E%3E%3Es!0s%25yA0s%22l%22l!r%26lengthb%3Ck%2Bl%22%5El%221%2Bs%22j%05l%20%20s%26l%26z0l!%24%20%2B%5B%22cs'(0l%23i'1ps9wxb%26s()%20%26%7Bs)%2Fs(gr%26Stringr%2CfromCharCodes)0s*yWl%20._b%26s%20o!%5D)l%20l%20Jb%3Ck%24.aj%3Bl%20.Tb%3Ck%24.gj%2Fl%20.%5Eb%3Ck%26i%22-4j!%1F%2B%26%20s%2ByPo!%5D%2Bs!l!l%20Hd%3E%26l!l%20Bd%3E%26%2Bl!l%20%3Cd%3E%26%2Bl!l%206d%3E%26%2Bl!l%20%26%2B%20s%2Cy%3Do!o!%5D%2Fq%2213o!l%20q%2210o!%5D%2Cl%202d%3E%26%20s.%7Bs-yMo!o!%5D0q%2213o!%5D*Ld%3Cl%204d%23%3E%3E%3Eb%7Cs!o!l%20q%2210o!%5D%2Cl!%26%20s%2FyIo!o!%5D.q%2213o!%5D%2Co!%5D*Jd%3Cl%206d%23%3E%3E%3Eb%7C%26o!%5D%2Bl%20%26%2B%20s0l-l!%26l-l!i'1z141z4b%2F%40d%3Cl%22b%7C%26%2Bl-l(l!b%5E%26%2Bl-l%26zl'g%2C)gk%7Dejo%7B%7Fcm%2C)%7Cyn~Lij~em%5B%22cl%24b%25%40d%3Cl%26zl'l%20%24%20%2B%5B%22cl%24b%25b%7C%26%2Bl-l%258d%3C%40b%7Cl!b%5E%26%2B%20q%24sign%20"), [TAC = {}]);tt = TAC.sign(user_id+"" + max_behot_time);

惊喜发现,将其嵌入到html中浏览器打开得到的参数是可以使用的,这是个好消息,接着就是冷水,当我使用htmlunit读取本地html时,也能得到参数,但是参数不可用,与方案二一样,然后我又将此网页嵌入到自己的网站,使用htmlunit模拟爬取自己的这个网页,同样,得到的参数也是不能使用。至此,陷入了困境!结果不重要(其实也很重要),享受的是过程!暂时先这样吧。

虽然不能获取后续加载的文章列表,但是首次加载的文章还是可以获取的,下面分析一下吧,文章列表都在那个json中

/*** @date Oct 17, 2018 12:30:46 PM* @Desc* @param blogMove* @param oneUrl* @return* @throws IOException* @throws MalformedURLException* @throws FailingHttpStatusCodeException*/public String getTouTiaoArticleUrlList(Blogmove blogMove, String oneUrl, List<String> urlList)throws FailingHttpStatusCodeException, MalformedURLException, IOException {String max_behot_time = "0";// 模拟浏览器操作// 创建WebClientWebClient webClient = new WebClient(BrowserVersion.CHROME);// 关闭css代码功能webClient.getOptions().setThrowExceptionOnScriptError(false);webClient.getOptions().setCssEnabled(false);// 如若有可能找不到文件js则加上这句代码webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);Map<String, String> additionalHeaders = new HashMap<String, String>();additionalHeaders.put("user-agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");WebRequest request = new WebRequest(new URL(oneUrl), HttpMethod.GET);request.setAdditionalHeaders(additionalHeaders);UnexpectedPage page2 = webClient.getPage(request);String result = IOUtils.toString(page2.getInputStream(), StandardCharsets.UTF_8);JSONObject json = JSON.parseObject(result);JSONArray jsonarray = (JSONArray) json.get("data");JSONObject json2 = (JSONObject) json.get("next");// 用于返回留下一页使用max_behot_time = json2.getString("max_behot_time");// 读取urlJSONObject jsonObject;String url;for (Object obj : jsonarray) {jsonObject = (JSONObject) obj;url = jsonObject.getString("source_url");if (url != null) {url = url.substring(6, url.length() - 1);if (urlList.size() < blogMove.getMoveNum()) {urlList.add("https://www.toutiao.com/i" + url);} else {break;}} else {url = "";continue;}}return max_behot_time;}

三.开干(获取文章具体信息)

接下来直接找一篇文章分析一下:https://www.toutiao.com/i6616645307906130435/

分析发现,数据在script标签中的变量BASE_DATA 中,解析即可得

代码

/*** @date Oct 17, 2018 12:46:52 PM* @Desc 获取详细信息* @param blogMove* @param url* @return* @throws IOException* @throws MalformedURLException* @throws FailingHttpStatusCodeException*/public Blogcontent getTouTiaoArticleMsg(Blogmove blogMove, String url, List<Blogcontent> bList)throws FailingHttpStatusCodeException, MalformedURLException, IOException {Blogcontent blogcontent = new Blogcontent();blogcontent.setArticleSource(blogMove.getMoveWebsiteId());// 模拟浏览器操作// 创建WebClientWebClient webClient = new WebClient(BrowserVersion.CHROME);// 关闭css代码功能webClient.getOptions().setThrowExceptionOnScriptError(false);webClient.getOptions().setCssEnabled(false);// 如若有可能找不到文件js则加上这句代码webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);// 获取第一级网页htmlHtmlPage page = webClient.getPage(url);Document doc = Jsoup.parse(page.asXml());Elements pageMsg22 = doc.body().getElementsByTag("script");String msgJsonString = BlogMoveTouTiaoUtils.getTouTiaoArticleMsgJsonString(pageMsg22);String content = null;String title = null;String author = null;Date date = null;if (msgJsonString != null && !"".equals(msgJsonString)) {// 解析JSONObject json = JSON.parseObject(msgJsonString);json = (JSONObject) json.get("articleInfo");content = json.getString("content");// 转义content = StringEscapeUtils.unescapeHtml3(content);title = json.getString("title");json = (JSONObject) json.get("subInfo");author = json.getString("source");date = DateUtils.formatStringDate(json.getString("time"), DateUtils.YYYY_MM_DD_HH_MM_SS);date = date == null ? new Date() : date;}// 是否重复去掉if (blogMove.getMoveRemoveRepeat() == 0) {// 判断是否重复if (BlogMoveCommonUtils.articleRepeat(bList, title)) {return null;}}blogcontent.setTitle(title);// 获取作者blogcontent.setAuthor(author);// 获取时间if (blogMove.getMoveUseOriginalTime() == 0) {blogcontent.setGtmCreate(date);} else {blogcontent.setGtmCreate(new Date());}blogcontent.setGtmModified(new Date());// 获取类型blogcontent.setType("原创");// 获取正文blogcontent.setContent(BlogMoveTouTiaoUtils.getTouTiaoArticleContent(content, blogMove, blogcontent));// 设置其他blogcontent.setStatus(blogMove.getMoveBlogStatus());blogcontent.setBlogColumnName(blogMove.getMoveColumn());// 特殊处理blogcontent.setArticleEditor(blogMove.getMoveArticleEditor());blogcontent.setShowId(DateUtils.format(new Date(), DateUtils.YYYYMMDDHHMMSSSSS));blogcontent.setAllowComment(0);blogcontent.setAllowPing(0);blogcontent.setAllowDownload(0);blogcontent.setShowIntroduction(1);blogcontent.setIntroduction("");blogcontent.setPrivateArticle(1);return blogcontent;}

幸运的是,图片下载没有遇到困难,效果:

欢迎交流学习!

完整源码请见github:https://github.com/ricozhou/blogmove​​​​​​​

博客搬家系列(六)-爬取今日头条文章相关推荐

  1. Python3爬取今日头条文章视频数据,完美解决as、cp、_signature的加密方法(2020-6-29版)

    前言 在这里我就不再一一介绍每个步骤的具体操作了,因为在爬取老版今日头条数据的时候都已经讲的非常清楚了,所以在这里我只会在重点上讲述这个是这么实现的,如果想要看具体步骤请先去看我今日头条的文章内容,里 ...

  2. 用python爬取头条文章_AI第四课:Python爬取今日头条文章

    到目前为止,能使用python写一点简单的程序了,本次的任务是爬取今日头条的文章信息. 大致涉及的知识点:json数据格式,浏览器插件jsonView,浏览器开发者模式,html基础,http代理,h ...

  3. [爬虫笔记01] Ajax爬取今日头条文章

    1.爬取分析 我们首先打开今日头条,搜索"罗志祥" 打开浏览器的开发者工具,红色框中就是我们请求到的数据 将搜索界面的滚动条滑到底,在开发者工具中就可以看到所有请求到的数据,加上前 ...

  4. java爬取今日头条文章

    闲来无事,写了个爬虫爬取今日头条的文章信息,然后使用ECharts展示出统计结果. 那么怎样爬取今日头条的信息呢? 首先,分析头条页面 文章是通过ajax获取的 所以要找到调用的url,然后跟踪代码查 ...

  5. python3 爬取今日头条文章(巧妙避开as,cp,_signature)

    使用环境: python3 scrapy win10 爬取思路 (一)关于as.cp的生成与_signature的想法 对于今日头条的爬虫,网上搜索出来的文章大多是基于崔庆才(通过搜索爬取美女街拍的方 ...

  6. 今日头条python_GitHub - a57571735/headlines_today: 基于Python的爬取今日头条文章及视频...

    分析所抓到的文章列表数据包:大致分为两类,一类是有视频的文章,一类则是没有视频的文章. 有视频的文章json内容里均有video_id这个key,如下图所示: 没有视频的文章:json文件内容均包含t ...

  7. python爬取今日头条文章json中data出现none_Python3爬取今日头条有关《人民的名义》文章...

    最近一直在看Python的基础语法知识,五一假期手痒痒想练练,正好<人民的名义>刚结束,于是决定扒一下头条上面的人名的名义文章,试试技术同时可以集中看一下大家的脑洞也是极好的. 首先,我们 ...

  8. python爬虫爬今日头条_GitHub - striver-ing/headlines_today: 基于Python的爬取今日头条文章及视频...

    分析所抓到的文章列表数据包:大致分为两类,一类是有视频的文章,一类则是没有视频的文章. 有视频的文章json内容里均有video_id这个key,如下图所示: 没有视频的文章:json文件内容均包含t ...

  9. 博客搬家系列(一)-简介

    这个功能思来想去想了很久,终于实现了基本功能,自己基于别人的后台权限管理系统写了一个博客系统,其实博客系统只是一小部分,但今天只讲博客部分,其他详见: RZSpider详见:https://blog. ...

最新文章

  1. java error could_Java.lang.Error: Properties init: Could not determine current working directory.
  2. jre,jdk,jvm的关系
  3. c语言delay_C语言编程制作“古怪手电筒”,有光的时候就会亮,没光绝不会亮...
  4. Nginx负载均衡+tomcat+session共享
  5. 『信息收集』GoogleHacking快速定位目标网站
  6. 【theano-windows】学习笔记十九——循环神经网络
  7. 【三维激光扫描】实验01:环境搭建CAD2014+StonexSiScan软件安装
  8. php 游标 上移,jQuery点击input使光标移动到最后或指定位置
  9. 您收到一封 2019 阿里云峰会 (北京) 邀请函
  10. Visual Studio 2010 实用功能总结 II
  11. android mm 修改路径,Android 编译系统模块
  12. SpringBoot之第一个Restfu示例
  13. 制定项目进度计划的过程
  14. 小程序 | 微信小程序布局左对齐自动换行
  15. KY-RTI分布仿真技术:第三章 KY-OMT对象模型模板工具
  16. linux中gimp命令截图,Linux利用GIMP截图
  17. 双拼对简拼和混拼的支持
  18. syntax error near unexpected token else
  19. 着急使用新西兰无犯罪文件怎么办理新西兰使馆认证呢
  20. 宁夏理工学院计算机是专科吗,宁夏理工学院是本科还是专科

热门文章

  1. 先验概率 后验概率 贝叶斯法则 贝叶斯公式
  2. Dorado7 notify非alert 输入框prompt confirm layer dialoger,layer.msg,toast效果,几秒关闭layer.load layer.open
  3. 直播预告 | 华南理工实验室专场二
  4. 华为路由器联动_华为路由WS5200怎么联动Yeelight智能设备
  5. 哪种耳机对听力伤害小?骨传导耳机能保护听力吗?
  6. Django—CRM项目练习
  7. 手牵手系列之TypeScript开发环境搭建
  8. 基于K-Means的银行客户数据集分析与处理
  9. codeforces1627C Not Assigning(思维)
  10. 我爱赚钱吧:你也可以通过建网站赚钱的④