* 建站数据SuperSpider(简书)
* 本项目目的:
* 为练习web开发提供相关的数据;
* 主要数据包括:
* 简书热门专题模块信息、对应模块下的热门文章、
* 文章的详细信息、作者信息、
* 评论区详细信息、评论者信息等...
* 最后存储mysql数据库.

想学习爬虫的同学也可以瞧瞧

整个项目跑完花了近十个小时, 足见数据之多, 个人web开发练习用来充当建站数据也是绰绰有余的(~ ̄▽ ̄)~

代码注释写的挺详细的,我就直接上代码了。

主要代码:

 ​

 /**
* 此类对简书文章内容页进行了详细的解析爬取;
* 封装后的唯一入口函数 {@link ArticleSpider#run(String, int)}.
*
* author As_
* date 2018-08-21 12:34:23
* github https://github.com/apknet
*/ public class ArticleSpider { /** 长度为4 :阅读量、评论数、点赞数、文章对应的评论id */
private List<Integer> readComLikeList = new ArrayList<>(); /** 文章Id */
private long articleId; /**
* 此类的入口函数;
* 爬虫范围包括文章的详情信息、评论的详细信息;
* 并同时持久化数据;
* key实例:443219930c5b
*
* @param key
* @param flag_type 文章所属分类的索引
*/ public void run(String key, int flag_type){ try {
articleSpider(key, flag_type); // 以下参数由articleSpeder()函数获得
String comUrl = String.format("https://www.jianshu.com/notes/%d/comments?comment_id=&author_only=false&since_id=0&max_id=1586510606000&order_by=desc&page=1", readComLikeList.get(3)); comSpider(comUrl); } catch (Exception e) {
e.printStackTrace();
}
} /**
* 链接实例 https://www.jianshu.com/p/443219930c5b
* 故只接受最后一段关键字
* @param key
* @param flag_type 文章所属分类的索引
* @throws Exception
*/
private void articleSpider(String key, int flag_type) throws Exception { String url = "https://www.jianshu.com/p/" + key; Document document = getDocument(url); IdUtils idUtils = new IdUtils(); List<Integer> list = getReadComLikeList(document); articleId = idUtils.genArticleId();
String title = getTitle(document);
String time = getTime(document);
String imgCover = getImgCover(document);
String brief = getBrief(document);
String content = getContent(document);
String author = idUtils.genUserId();
int type = flag_type;
int readNum = list.get(0);
int comNum = list.get(1);
int likeNum = list.get(2); // 数据库添加对应对象数据
Article article = new Article(articleId, title, time, imgCover, brief, content, author, type, readNum, comNum, likeNum);
new ArticleDao().addArticle(article); User user = new User();
user.setUser(idUtils.genUserId());
user.setName(getAuthorName(document));
user.setImgAvatar(getAuthorAvatar(document));
new UserDao().addUser(user); } private Document getDocument(String url) throws IOException { Document document = Jsoup.connect(url)
.header("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0")
.header("Content-Type", "application/json; charset=utf-8")
.header("Cookie", "_m7e_session=2e930226548924f569cb27ba833346db;locale=zh-CN;default_font=font2;read_mode=day")
.get();
return document;
} private String getTitle(Document doc){
return doc.select("h1[class=title]").text();
} private String getTime(Document doc){
Element element = doc.select("span[class=publish-time]").first();
return element.text();
} private String getImgCover(Document doc){
Element element = doc.select("img[data-original-format=image/jpeg]").first();
if (element.hasAttr("data-original-src")){
String url = element.attr("data-original-src").trim();
return SpiderUtils.handleUrl(url);
}
return null;
} private String getBrief(Document doc){
return doc.select("meta[name=description]").first().attr("content");
} private String getContent(Document doc){
Element element = doc.select("div[class=show-content-free]").first();
// System.out.println(element.html()); Elements eles = element.select("div[class=image-package]");
for(Element ele: eles){
Element imgEle = ele.select("img").first();
ele.replaceWith(imgEle);
} String result = element.html().replaceAll("data-original-", "");
return result;
} private String getAuthorAvatar(Document doc){
String url = doc.select("div[class=author]").select("img").attr("src");
return SpiderUtils.handleUrl(url);
} private String getAuthorName(Document doc){
Element element = doc.select("script[data-name=page-data]").first();
JSONObject jsonObject = new JSONObject(element.html()).getJSONObject("note").getJSONObject("author");
return jsonObject.getString("nickname");
} private List<Integer> getReadComLikeList(Document doc){
Element element = doc.select("script[data-name=page-data]").first();
JSONObject jsonObject = new JSONObject(element.html()).getJSONObject("note"); readComLikeList.add(jsonObject.getInt("views_count"));
readComLikeList.add(jsonObject.getInt("comments_count"));
readComLikeList.add(jsonObject.getInt("likes_count"));
readComLikeList.add(jsonObject.getInt("id")); System.out.println(Arrays.toString(readComLikeList.toArray()));
return readComLikeList;
} /**
* 评论区爬虫---包括:
* 评论楼层、时间、内容、评论者、评论回复信息等
* 具体可查看{@link Comment}
*
* @param url
* @throws Exception
*/
private void comSpider(String url) throws Exception {
URLConnection connection = new URL(url).openConnection(); // 需加上Accept header,不然导致406
connection.setRequestProperty("Accept", "*/*"); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line;
StringBuilder str = new StringBuilder(); while ((line = reader.readLine()) != null) {
str.append(line);
} System.out.println(str.toString()); JSONObject jb = new JSONObject(str.toString()); boolean hasComment = jb.getBoolean("comment_exist");
if(hasComment){
int pages = jb.getInt("total_pages");
if(pages > 20){
// 某些热门文章评论成千上万, 此时没必要全部爬取
pages = 20;
}
int curPage = jb.getInt("page"); JSONArray jsonArray = jb.getJSONArray("comments");
Iterator iterator = jsonArray.iterator();
while(iterator.hasNext()){
JSONObject comment = (JSONObject) iterator.next();
JSONArray comChildren = comment.getJSONArray("children");
JSONObject userObj = comment.getJSONObject("user");
IdUtils idUtils = new IdUtils();
CommentDao commentDao = new CommentDao();
UserDao userDao = new UserDao(); int commentId = idUtils.genCommentId();
String userId = idUtils.genUserId(); System.out.println(comment.toString());
// 评论内容相关
String content = comment.getString("compiled_content");
String time = comment.getString("created_at");
int floor = comment.getInt("floor");
int likeNum = comment.getInt("likes_count");
int parentId = 0; // 评论者信息
String name = userObj.getString("nickname");
String avatar = userObj.getString("avatar"); Comment newCom = new Comment(commentId, articleId, content, time, floor, likeNum, parentId, userId, avatar, name);
commentDao.addComment(newCom); User user = new User();
user.setUser(userId);
user.setName(name);
user.setImgAvatar(avatar); userDao.addUser(user); // 爬取评论中的回复
Iterator childrenIte = comChildren.iterator();
while(childrenIte.hasNext()){
JSONObject child = (JSONObject) childrenIte.next(); Comment childCom = new Comment();
childCom.setId(idUtils.genCommentId());
childCom.setArticleId(articleId);
childCom.setComment(child.getString("compiled_content"));
childCom.setTime(child.getString("created_at"));
childCom.setParentId(child.getInt("parent_id"));
childCom.setFloor(floor);
childCom.setNameAuthor(child.getJSONObject("user").getString("nickname")); commentDao.addComment(childCom); }
} // 实现自动翻页
if(curPage == 1){
for(int i = 2; i <= pages; i++){
System.out.println("page-------------------> " + i);
int index = url.indexOf("page=");
String sub_url = url.substring(0, index + 5);
String nextPage = sub_url + i;
comSpider(nextPage);
}
}
}
}
} ​

and:

 ​

 /**
* 模块(简书的专题)爬虫
* 入口:https://www.jianshu.com/recommendations/collections
* 模块实例: https://www.jianshu.com/c/V2CqjW
* 文章实例: https://www.jianshu.com/p/443219930c5b
* 故此爬虫多处直接传递链接后缀的关键字符串(如‘V2CqjW’、‘443219930c5b’)
*
* @author As_
* @date 2018-08-21 12:31:35
* @github https://github.com/apknet
*
*/ public class ModuleSpider { public void run() {
try { List<String> moduleList = getModuleList("https://www.jianshu.com/recommendations/collections"); for (String key: moduleList) {
// 每个Module爬取10页内容的文章
System.out.println((moduleList.indexOf(key) + 1) + "." + key);
for (int page = 1; page < 11; page++) {
String moduleUrl = String.format("https://www.jianshu.com/c/%s?order_by=top&page=%d", key, page); List<String> articleList = getArticleList(moduleUrl); for (String articlekey: articleList) {
new ArticleSpider().run(articlekey, moduleList.indexOf(key) + 1);
}
}
} } catch (Exception e) {
e.printStackTrace();
}
} /**
* 返回Module 关键字符段
* @param url
* @return
* @throws IOException
* @throws SQLException
*/ private List<String> getModuleList(String url) throws IOException, SQLException { List<String> moduleList = new ArrayList<>();
int i = 0; Document document = Jsoup.connect(url).get();
Element container = document.selectFirst("div[id=list-container]");
Elements modules = container.children();
for(Element ele: modules){
String relUrl = ele.select("a[target=_blank]").attr("href");
String moduleKey = relUrl.substring(relUrl.indexOf("/c/") + 3);
moduleList.add(moduleKey); // System.out.println(moduleKey);
// 以下爬取数据创建Module对象并持久化 String name = ele.select("h4[class=name]").text();
String brief = ele.selectFirst("p[class=collection-description]").text();
String imgcover = SpiderUtils.handleUrl(ele.selectFirst("img[class=avatar-collection]").attr("src")); String articleNu = ele.select("a[target=_blank]").get(1).text();
int articleNum = Integer.parseInt(articleNu.substring(0, articleNu.indexOf("篇"))); String userNu = ele.select("div[class=count]").text().replace(articleNu, "");
Matcher matcher = Pattern.compile("(\\D*)(\\d*\\.\\d*)?(\\D*)").matcher(userNu);
matcher.find();
int userNum = (int)(Float.parseFloat(matcher.group(2)) * 1000); Module module = new Module((++i), name, brief, imgcover, userNum, articleNum, "apknet");
new ModuleDao().addModule(module); System.out.println(name + "------------------>");
}
return moduleList;
} /**
* 文章链接实例 https://www.jianshu.com/p/443219930c5b
* 故此处返回List的String为url后的那段字符串
*
* @param url
* @return
*/
private List<String> getArticleList(String url) { System.out.println("getArticleList: --------------------->"); List<String> keyList = new ArrayList<>(); Document document = null;
try {
document = Jsoup.connect(url).get();
} catch (Exception e) {
e.printStackTrace();
}
Element ulElement = document.select("ul[class=note-list]").first(); Elements elements = ulElement.select("li[id]");
for (Element ele : elements) {
String relUrl = ele.select("a[class=title]").attr("href");
String key = relUrl.substring(relUrl.indexOf("/p/") + 3);
keyList.add(key);
System.out.println(key);
} return keyList;
} } ​

最后附上实验截图:

​​

完整项目地址:

https://github.com/apknet/SuperSpider

SuperSpider(简书爬虫JAVA版)的更多相关文章

  1. jsoup爬虫简书首页数据做个小Demo

    代码地址如下:http://www.demodashi.com/demo/11643.html 昨天LZ去面试,遇到一个大牛,被血虐一番,发现自己基础还是很薄弱,对java一些原理掌握的还是不够稳固, ...

  2. 网页爬虫的设计与实现(Java版)

    网页爬虫的设计与实现(Java版)     最近为了练手而且对网页爬虫也挺感兴趣,决定自己写一个网页爬虫程序. 首先看看爬虫都应该有哪些功能. 内容来自(http://www.ibm.com/deve ...

  3. 《Java核心技术 卷II 高级特性(原书第9版)》

    <Java核心技术 卷II 高级特性(原书第9版)> 基本信息 原书名:Core Java Volume II—Advanced Features(Ninth Edition) 作者: ( ...

  4. Java native代码编译步骤简书

    Java native代码编译步骤简书 目的:防止java代码反编译获取密码算法 (1)编写实现类com.godlet.PasswordAuth.java (2)编译java代码javac Passw ...

  5. 学习PHP爬虫--《Webbots、Spiders和Screen Scrapers:技术解析与应用实践(原书第2版)》

    <Webbots.Spiders和Screen Scrapers:技术解析与应用实践(原书第2版)> 译者序 前言 第一部分 基础概念和技术 第1章 本书主要内容3 1.1 发现互联网的真 ...

  6. [Selenium2+python2.7][Scrap]爬虫和selenium方式下拉滚动条获取简书作者目录并且生成Markdown格式目录

    预计阅读时间: 15分钟 环境: win7 + Selenium2.53.6+python2.7 +Firefox 45.2  (具体配置参考 http://www.cnblogs.com/yoyok ...

  7. Node爬取简书首页文章

    Node爬取简书首页文章 博主刚学node,打算写个爬虫练练手,这次的爬虫目标是简书的首页文章 流程分析 使用superagent发送http请求到服务端,获取HTML文本 用cheerio解析获得的 ...

  8. Python 2.7_发送简书关注的专题作者最新一篇文章及连接到邮件_20161218

    最近看简书文章关注了几个专题作者,写的文章都不错,对爬虫和数据分析都写的挺好,因此想到能不能获取最新的文章推送到Ipad网易邮箱大师.邮件发送代码封装成一个函数,从廖雪峰大神那里学的  http:// ...

  9. PetaPojo —— JAVA版的PetaPoco

    背景 由于工作的一些原因,需要从C#转成JAVA.之前PetaPoco用得真是非常舒服,在学习JAVA的过程中熟悉了一下JAVA的数据组件: MyBatis 非常流行,代码生成也很成熟,性能也很好.但 ...

随机推荐

  1. 开发一个属于自己的第一个Composer/Packagist包

    Composer 给我们带来了诸多的好处: 模块化,降低代码重用成本 统一的第三方代码组织方式 更科学的版本更新 初始化项目,生成composer.json文件 初始实例项目代码目录结构: 现在要在项 ...

  2. Java实现终止线程池中正在运行的定时任务

    源于开发 最近项目中遇到了一个新的需求,就是实现一个可以动态添加定时任务的功能.说到这里,有人可能会说简单啊,使用quartz就好了,简单粗暴.然而quartz框架太重了,小项目根本不好操作啊.当然, ...

  3. JVM各区域的用途

    程序计数器 用于给字节码解释器来选取吓一跳需要执行的字节码指令.每个线程有一个独立的程序计数器去,且各个线程之间互不影响.如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令 ...

  4. jmeter 阶梯式加压测试

    性能测试中,有时需要模拟一种实际生产中经常出现的情况,即:从某个值开始不断增加压力,直至达到某个值,然后持续运行一段时间. 在jmeter中,有这样一个插件,可以帮我们实现这个功能,这个插件就是:St ...

  5. 2018沈阳网赛F--上下界网络流

    建图: 首先加一个源点s和汇点t,分别连接在二分图的左边和右边,每条弧的上下界为[L, R],二分图左边和右边之间连弧上下界为[0,1],其实就相当于连弧为1. 然后问题就转换为:有源汇最大流. 继续 ...

  6. oracle 多表连接查询 join(一)

    一.简介: 多表连接查询通过表之间的关联字段,一次查询多表数据. 下面将依次介绍 多表连接中的如下方法: 1.from a,b 2.inner join 3.left outer join 4.rig ...

  7. P2253 好一个一中腰鼓!

    题意:给你一个序列,初始是0,每次一个操作,把一个数^=1 每次求出最长01串的长度 正解:线段树(虽然暴力能过) 对于每个区间,记录三个值 lmax,以l为首的01串长度 rmax,以r为尾的01串 ...

  8. HDU6298 Maximum Multiple (多校第一场1001)

    Maximum Multiple Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  9. 详解linux下批量替换文件内容的三种方法(perl,sed,shell)

    在建设本网站的时候,发现新建了很多的网页,突然发现,每个文件都需要进行修改一样的内容,一个一个打开很是麻烦,所以,总结了一下如何快速修改一个目录下多个文件进行内容替换.第三种方法用的不多 方法一 使用 ...

  10. PANet训练自己的数据(VIA标注)

    当前最好的实例分割网络非PANet莫属,可是由于模型太新,网上的资料太少,最近的项目需要 实例分割,只能自己踩踩坑了,目前我还没看到一篇关于PANet训练的博客,只有几篇讲论文的. 环境:ubuntu ...