前言

前些天无意间在百度搜索了一下以前写过的博客

我啥时候在这么多不知名的网站上发表博客了???点进去一看, 内容一模一样,作者却不是我...

然后又去搜了其他篇博客,果然,基本上每篇都在别的网站上有,细想,可能是通过网络爬虫爬取博客园首页博客,然后copy至自己网站中,于是乎,博主也来实现一遍爬取流程。。。

实现思路

先访问博客园首页,F12查看源代码,可以看到博客的链接和标题都是放在一个a标签里,

点击一下上一下、下一页,再看一下请求参数,嗯。。。这个应该是页码参数

通过以上这些信息,我们就可以知道只需要每次传入不同的页码访问博客园首页,就可以获得相应博客的html页面返回,然后我们返回的html页面,解析出当页的博客链接和标题就可以啦。

说干就干,下面我们用代码实现模拟下载博客园200页(200 * 20 = 4000篇)博文的程序

具体实现

直接上代码了,注释都在代码中

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern; /**
* @ClassName BKYPageReptile
* @Description TODO(爬取博客园文章)
* @Author 我恰芙蓉王
* @Date 2020年08月11日 9:38
* @Version 2.0.0
**/ public class BKYPageReptile { //请求地址
private static final String URL = "https://www.cnblogs.com"; //保存路径
private static final String TARGET_PATH = "F://" + "博客园"; //行匹配正则
private static final Pattern LINE_PATTERN = Pattern.compile("<a class=\"post-item-title\" href=\"https://www.cnblogs.com/.*?\\.html\" target=\"_blank\">.*?</a>"); //url正则
private static final Pattern URL_PATTERN = Pattern.compile("https://www.cnblogs.com/.*?\\.html"); //标题/文件名正则
private static final Pattern TITLE_PATTERN = Pattern.compile(">.*?</a>"); //标题缓存
private static final List<String> TITLE_LIST = new CopyOnWriteArrayList<>(); //当前页数
private static int PAGE = 1; //最大拉取页数
private static final int MAX_PAGE = 200; //一共拉取博客篇数
private static int ALL_COUNT = 0; //时间格式
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) {
//创建根目录
File rootDir = new File(TARGET_PATH);
if (!rootDir.exists()) {
rootDir.mkdir();
} //创建日志文件夹
String logPath = TARGET_PATH + "//拉取日志";
File logDir = new File(logPath);
if (!logDir.exists()) {
logDir.mkdir();
} //创建日志文件
File logFile = new File(logPath + "//log.txt");
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
} //循环拉取
while (PAGE <= MAX_PAGE) {
//日志内容
String logContent = "正在拉取第" + PAGE + "页\n";
System.err.println("\n" + logContent);
String param = "PageIndex=" + PAGE; try {
//获取指定页页返回内容
String response = sendPost(URL, param); Matcher matcher = LINE_PATTERN.matcher(response); //需要写入的文件集合
ArrayList<FileTemplate> urlList = new ArrayList<>(20); /**
* 解析返回内容封装成FileTemplate
*/
while (matcher.find()) {
//匹配行
String matchLine = matcher.group(); Matcher matcher1 = TITLE_PATTERN.matcher(matchLine);
String title = null;
while (matcher1.find()) {
//匹配的标题 >标题</a>
title = matcher1.group();
}
//截取拿到真实标题
title = title.substring(1, title.length() - 4);
//特殊字符处理
title = title.replace("<", "《")
.replace(">", "》")
.replace("\\", "-")
.replace("/", "-")
.replace(":", ":")
.replace("*", "")
.replace("?", "?")
.replace("|", "")
+ ".html";
System.err.println("title = " + title); //如果已经拉取了此标题的html文件 则跳过此篇
if (TITLE_LIST.contains(title)) {
continue;
} Matcher matcher2 = URL_PATTERN.matcher(matchLine);
String url = null;
while (matcher2.find()) {
//匹配博客的请求url
url = matcher2.group();
}
//封装成文件模板对象
urlList.add(new FileTemplate(url, title, false));
} /**
* 写入磁盘
*/
urlList.parallelStream().forEach(v -> {
FileOutputStream fos = null;
PrintWriter pw = null;
try {
String result = sendGet(v.getGetUrl(), "");
File file = new File(TARGET_PATH + File.separator + v.getTitle());
file.createNewFile(); fos = new FileOutputStream(file);
pw = new PrintWriter(fos);
pw.write(result.toCharArray());
pw.flush();
v.setFlag(true); TITLE_LIST.add(v.getTitle());
} catch (Exception e) {
System.out.println(v.toString());
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
if (pw != null) {
pw.close();
}
} catch (IOException e) {
e.printStackTrace();
} }
}); /**
* 记录日志
*/
//本次写入成功博客数
long count = urlList.stream().filter(v -> v.getFlag()).count(); String date = SDF.format(new Date()); //累加次数
ALL_COUNT += count; logContent += "本次拉取完成,共 " + count + " 篇新博客\r\n";
logContent += "一共拉取了 " + ALL_COUNT + " 篇\r\n";
logContent += "时间 : " + date + "\n\n";
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
out.write(logContent + "\r\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} PAGE++;
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 文件模板类
*/
private static class FileTemplate {
/**
* 请求地址
*/
private String getUrl; /**
* 标题
*/
private String title; /**
* 已经爬取标识
*/
private boolean flag; public FileTemplate(String getUrl, String title, boolean flag) {
this.getUrl = getUrl;
this.title = title;
this.flag = flag;
} public String getGetUrl() {
return getUrl;
} public void setGetUrl(String getUrl) {
this.getUrl = getUrl;
} public String getTitle() {
return title;
} public void setTitle(String title) {
this.title = title;
} @Override
public String toString() {
final StringBuilder sb = new StringBuilder("FileTemplate{");
sb.append("getUrl='").append(getUrl).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append('}');
return sb.toString();
} public boolean getFlag() {
return flag;
} public void setFlag(boolean flag) {
this.flag = flag;
}
} /**
* 功能描述: 向指定URL发送GET请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式
* @创建人: 我恰芙蓉王
* @创建时间: 2020年08月11日 16:42:17
* @return: java.lang.String 响应结果
**/
public static String sendGet(String url, String param) {
StringBuilder sb = new StringBuilder();
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return sb.toString();
} /**
* 功能描述: 向指定URL发送POST请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式
* @创建人: 我恰芙蓉王
* @创建时间: 2020年08月11日 16:42:17
* @return: java.lang.String 响应结果
**/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder sb = new StringBuilder();
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return sb.toString();
}
}

测试结果

控制台输出

下载在电脑磁盘中

日志文件内容

随便打开一个html文件

java实现一个简单的爬虫小程序的更多相关文章

  1. 利用java开发一个双击执行的小程序

    之前我们利用java写了很多东西,但是好像都没有什么实际意义. 因为有意义桌面小程序怎么都得有个界面,可是界面又不太好搞.或者 了解到这一层的人就少之又少了. 呀,是不是还得开辟一些版面来介绍awt和 ...

  2. 使用vue+koa实现一个简单的图书小程序(1)

    这个系列的博客用来记录我开发时候遇到的问题以及学习到的知识 边做边学: 前后端分离,高内聚低耦合小程序端使用了mpvue 内部使用了vuejs的语法 来做整个小程序的渲染层 后端使用的是koa2搭建一 ...

  3. [C#] Socket 通讯,一个简单的聊天窗口小程序

    Socket,这玩意,当时不会的时候,抄别人的都用不好,简单的一句话形容就是“笨死了”:也是很多人写的太复杂,不容易理解造成的.最近在搞erlang和C的通讯,也想试试erlang是不是可以和C#简单 ...

  4. 一个简单的servlet小程序

    servlet是不能单独运行的,他是运行在web服务器或应用服务器上的java程序,或者可以说是在servlet容器上运行的,我们经常使用到的tomcat就是一个servlet容器. 他是处理HTTP ...

  5. python写的的简单的爬虫小程序

    import re import urllib def getHtml(url): page=urllib.urlopen(url) html=page.read() return html def ...

  6. 福利贴——爬取美女图片的Java爬虫小程序代码

    自己做的一个Java爬虫小程序 废话不多说.先上图. 目录命名是用标签缩写,假设大家看得不顺眼能够等完成下载后手动改一下,比方像有强迫症的我一样... 这是挂了一个晚上下载的总大小,只是还有非常多由于 ...

  7. 一个python爬虫小程序

    起因 深夜忽然想下载一点电子书来扩充一下kindle,就想起来python学得太浅,什么“装饰器”啊.“多线程”啊都没有学到. 想到廖雪峰大神的python教程很经典.很著名.就想找找有木有pdf版的 ...

  8. Java实现一个简单的网络爬虫

    Java实现一个简单的网络爬虫 import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWri ...

  9. IOS开发之小实例--使用UIImagePickerController创建一个简单的相机应用程序

    前言:本篇博文是本人阅读国外的IOS Programming Tutorial的一篇入门文章的学习过程总结,难度不大,因为是入门.主要是入门UIImagePickerController这个控制器,那 ...

随机推荐

  1. GPO - General GPO Settings(3)

    WMI filtering Setting - Differentiating Installation Between Operations and Architecture. WMI SQL Ge ...

  2. markdownpad2初使用

      本来是想在csdn上写blog的,到那时不知道为什么,那个写blog的界面总是崩溃,写了半天的东西和公式也都没有了,很气愤,所以就准备下载一个本地的markdown编辑器,下载课两款一款是mark ...

  3. 导出Telegram贴纸

    如何导出Telegram的贴纸1.在Telegram中 @StickerSetBot 机器人2.输入 /newpack 开启机器人,会提示 OK now send me stickers or sti ...

  4. 修改map中原来的各种Key

    简单描述: 做数据迁移的时候,需要展示数据库的字段信息,但是我发现 Oracle的sql查询到的结果 出来默认是大写的 和 前端vue的参数小写开头+驼峰 不太一样 所以后台取到的数据都是是引用Lis ...

  5. BUUCTF-web ikun(Python 反序列化)

    正如本题所说,脑洞有点大.考点还很多,不过最核心的还是python的pickle反序列化漏洞 题目中暗示了要6级号,找了很多页都没看到,于是写了脚本 在第180页有6级号,但是价格出奇的高,明显买不起 ...

  6. Android复习准备

    1. 四大组件是什么? Activity(活动):用于表现功能 Service(服务):后台运行服务,不提供界面呈现 BroadcastReceiver(广播接收器):用来接收广播 ContentPr ...

  7. C#中使用ajax请求

    ajax简介 Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式.快速动态网页应用的网页开发技术,无需重新加载 ...

  8. Python异常及异常处理

    Python异常及异常处理: 当程序运行时,发生的错误称为异常 例: 0 不能作为除数:ZeroDivisionError 变量未定义:NameError 不同类型进行相加:TypeError 异常处 ...

  9. PHP rename() 函数

    定义和用法 rename() 函数重命名文件或目录. 如果成功,该函数返回 TRUE.如果失败,则返回 FALSE. 语法 rename(oldname,newname,context) 参数 描述 ...

  10. CF EC 86 E Placing Rooks 组合数学

    LINK:Placing Rooks 丢人现场.jpg 没看到题目中的条件 放n个rook 我以为可以无限放 自闭了好半天. 其实只用放n个.那么就容易很多了. 可以发现 不管怎么放 所有列/所有行 ...