文档章节

批量导出某个简书用户的所有文章列表和文章超链接

JerryWang_SAP
 JerryWang_SAP
发布于 05/09 21:53
字数 1036
阅读 1
收藏 0

简书改版后,根据文章标题搜索文章的功能就不见了。

虽然简书提供了批量下载文章的功能,但是下载到本地的文章都是markdown格式的,不包含文章的链接,这不满足我的需求。

既然我是程序员,没有这个功能我就自己实现一个。

打开简书首页,发现默认只显示8篇文章,用鼠标滑动到屏幕底部后,会触发一个懒加载事件,到后台读取更多的文章列表,所以文章读取在服务器端是采取的分页实现。

打开Chrome开发者工具,观察网络请求,请求url中99b8712e8850是我简书用户id,page=2,3,4这些是分页代码。

每页的文章内容以html格式包含在响应结构里:

我关心的只是文章标题和文章链接,如上图高亮字段所示。

最开始我写了一个nodejs应用,代码如下:

var request = require('request');
var jsdom = require("jsdom");
var JSDOM = jsdom.JSDOM;
const PREFIX = "https://www.jianshu.com";
const PAGE = "https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";
const MAX = 2;

var mArticleResult = new Map();
var pageNumber;
/* a given article: https://www.jianshu.com/p/963cd23fb092
  value got from API: /p/5c1d0319dc42
*/
var lastPageReached = false;
var url = "";

var aHandlers = [];

// use limited for loop to ease testing
for(var i = 0; i < MAX; i++){
  pageNumber = i + 1;
  var url = PAGE + pageNumber;
  // console.log("current page: " + url);
  var pageOptions = {
        url: url,
        method: "GET",
        headers: {
            "Accept": "text/html"
        }
  };
  aHandlers.push(getArticles(pageOptions, pageNumber));
  if( lastPageReached)
    break;
}

console.log("promise handler size: " + aHandlers.length);

Promise.all(aHandlers).then(function(){
  var articleIndex = 0;
  for (var [key, value] of mArticleResult) {
    console.log("Article[" + articleIndex++ + "]: " + key + " = " + value);
  }
  console.log("done");
}
  );

function getArticles(pageOptions, pageNumber) {
  return new Promise(function(resolve,reject){
      var requestC = request.defaults({jar: true});

      requestC(pageOptions,function(error,response,body){
        if( error){
          console.log("error: " + error);
          resolve(error);
        }
        var document = new JSDOM(body).window.document;
        var content = document.getElementsByTagName("li");

        for( var i =0; i < content.length; i++){
          var li = content[i];
          var children = li.childNodes;
          for( var j = 0; j < children.length; j++){
              var eachChild = children[j];
              if( eachChild.nodeName == "DIV"){
                var grandChild = eachChild.childNodes;
                for( var k = 0; k < grandChild.length; k++){
                  var grand = grandChild[k];
                  if( grand.nodeName == "A"){
                    var fragment = grand.getAttribute("href");
                    if( fragment.indexOf("/p") < 0)
                      continue;
                    console.log("title: " + grand.text);
                    var wholeURL = PREFIX + fragment;
                    console.log("url: " + wholeURL);
                    if( mArticleResult.has(grand.text)){
                      lastPageReached = true;
                      console.log("article size: " + mArticleResult.size);
                      resolve(pageNumber);
                    }
                    mArticleResult.set(grand.text, wholeURL);
                  }
                }
              }
          }
        }// end of outer loop
        resolve(pageNumber);
      }); 
     });
}

原理就是使用nodejs的request module,向简书网站同时发起多个请求,每个请求读取一页的简书文章。

后来发现这种方法在并发请求数大于10个的时候就无法工作,简书网站会拒绝该类请求,返回HTTP 429状态码。

所以最后我采用了最简单的同步请求实现,使用了nodejs提供的sync-request在循环里发起请求。

var request = require("sync-request");
var jsdom = require("jsdom");
var JSDOM = jsdom.JSDOM;
var textEncoding = require('text-encoding'); 
var textDecoder = textEncoding.TextDecoder;

const PREFIX = "https://www.jianshu.com";
const PAGE = "https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";
const MAX = 100;

var mArticleResult = new Map();
var lastPageReached = false;
var pageNumber;
/* a given article: https://www.jianshu.com/p/963cd23fb092
  value got from API: /p/5c1d0319dc42
*/

try {
    // use limited for loop to ease testing
    for (var i = 0; i < MAX; i++) {
        if( lastPageReached)
          break;
        pageNumber = i + 1;
        var url = PAGE + pageNumber;
        console.log("current page: " + url);
        var response = request('GET', url);
        var html = new textDecoder("utf-8").decode(response.body);
        handleResponseHTML(html);
    }
} 
catch (e) {

}

var articleIndex = 0;
var resultHTML = "<html>";

const fs = require('fs');

/*
<HTML>
<p>
<a href="https://www.baidu.com">eee</a>
</p>

<p><a>22</a></p>
<p><a>33</a></p>
</HTML>
*/

var index = 1;
for (var [key, value] of mArticleResult) {
    var article = "<p><a href=\"" + key + "\">" + 
    index++ + ". " + value + "</a></p>" + "\n";
    resultHTML = resultHTML + article;
    console.log("Article[" + articleIndex++ + "]: " + value + " = " + key);
}

resultHTML = resultHTML + "</html>";

var pwd = process.cwd() + "/jianshu.html";

fs.appendFileSync(pwd, resultHTML);

console.log("done");



function handleResponseHTML(html) {
    var document = new JSDOM(html).window.document;
    var content = document.getElementsByTagName("li");

    for (var i = 0; i < content.length; i++) {
        var li = content[i];
        var children = li.childNodes;
        for (var j = 0; j < children.length; j++) {
            var eachChild = children[j];
            if (eachChild.nodeName == "DIV") {
                var grandChild = eachChild.childNodes;
                for (var k = 0; k < grandChild.length; k++) {
                    var grand = grandChild[k];
                    if (grand.nodeName == "A") {
                        var fragment = grand.getAttribute("href");
                        if (fragment.indexOf("/p") < 0)
                            continue;
                        // console.log("title: " + grand.text);
                        var wholeURL = PREFIX + fragment;
                        // console.log("url: " + wholeURL);
                        if (mArticleResult.has(wholeURL)) {
                            lastPageReached = true;
                            console.log("article size: " + mArticleResult.size);
                            return;
                        }
                        mArticleResult.set(wholeURL, grand.text);
                    }
                }
            }
        }
    }
}

这个nodejs应用执行后,会在本地生成一个html文件,包含每篇文章的标题和超链接。

要获取更多Jerry的原创文章,请关注公众号"汪子熙":

© 著作权归作者所有

JerryWang_SAP
粉丝 24
博文 687
码字总数 553663
作品 0
深圳
程序员
私信 提问
DEDE数据库语句 DEDESQL命令批量替换 SQL执行语句

利用SQL命令批量修 DEDE数据库语句 DEDESQL命令批量替换 SQL执行语句 1.更改文章中的内容 Dede教程-598080707.NET update dede_addonarticle set body=replace(body,'原来的字符','替换后的字......

可淘淘
2011/10/27
2.3K
0
极致阅读体验:简书IOS功能拆解分析

最近迷上了简书。也来说说简书IOS的功能设计吧。当然,WEB版我也很喜欢。评测的逻辑如下:   痛点->主功能->使用体验是否能够最优实现主功能->设计   一、解决的痛点:   WEB版:整理书...

程序员客栈
2016/04/28
29
0
告别简书,自定义简书首页 & 图片数据备份

写这篇文章的原因,想必大家都知道了,在简书学到的东西很多,简书记录着我的成长,我心灵也原来越变得更加纯洁一点,而且最牛的一点是 我学会了怎么起标题。 文章写的好,不如标题起的好。但...

编程之乐
2017/12/13
0
0
简书,我对你有点小想法

图片来自地球 第一次知道有简书这个平台大概是2年前,当时的我在查阅某些学习资料的过程中偶然发现简书的存在。 打开官网,看到界面的排版和界面的颜色搭配,瞬间给我一种清新简约的感觉,简...

鼠急跳墙
2017/11/06
0
0
一些EXCHANGE命令

烟台小崔 的BLOG:http://seawind.blog.51cto.com/6845370/1883317 2016-12-16 14:10:09 标签:EXCHANGE命令 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本...

yqcdvip
2017/09/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周一乱弹 —— 熟悉的味道,难道这就是恋爱的感觉

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @xiaoshiyue :好久没分享歌了分享张碧晨的单曲《今后我与自己流浪》 《今后我与自己流浪》- 张碧晨 手机党少年们想听歌,请使劲儿戳(这里)...

小小编辑
今天
605
15
SpringBoot中 集成 redisTemplate 对 Redis 的操作(二)

SpringBoot中 集成 redisTemplate 对 Redis 的操作(二) List 类型的操作 1、 向列表左侧添加数据 Long leftPush = redisTemplate.opsForList().leftPush("name", name); 2、 向列表右......

TcWong
今天
19
0
排序––快速排序(二)

根据排序––快速排序(一)的描述,现准备写一个快速排序的主体框架: 1、首先需要设置一个枢轴元素即setPivot(int i); 2、然后需要与枢轴元素进行比较即int comparePivot(int j); 3、最后...

FAT_mt
昨天
4
0
mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
昨天
12
0
golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架...

非正式解决方案
昨天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部