前言

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

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

然后又去搜了其他篇博客,果然,基本上每篇都在别的网站上有,细想,可能是通过网络爬虫爬取博客园首页博客,然后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. Burp Suite Decoder Module - 解码模块

    官方参考链接:https://portswigger.net/burp/documentation/desktop/tools/decoder 该模块主要进行编码和解码,支持编码方式有:Plain,U ...

  2. Ethical Hacking - GAINING ACCESS(7)

    Server Side Attacks - NEXPOSE NeXpose is a vulnerability management framework, it allows us to disco ...

  3. [开源硬件DIY] 自制一款精致炫酷的蓝牙土壤温湿度传感器,用于做盆栽呵护类产品(API开放,开发者可自行DIY微信小程序\安卓IOS应用)

    目录 前言: 1. 成品展示 2. 原理图解析 3. pcb设计 4. 嵌入式对外提供接口 4.1 蓝牙广播 4.2 蓝牙服务和属性 4.3 数据包格式 4.4 数据通信模型 重要 . 前言: 本期给 ...

  4. p40_数据交换方式

    一.为什么要数据交换 数据链路发展史: 二.数据交换方式 电路交换 报文交换 分组交换[数据报方式,虚电路方式] 三.电路交换 eg:电话网络(特点:**独占资源,**即使两个人不说话,链接也不会被别 ...

  5. 【JVM之内存与垃圾回收篇】堆

    堆 堆的核心概念 堆针对一个 JVM 进程来说是唯一的,也就是一个进程只有一个 JVM,但是进程包含多个线程,他们是共享同一堆空间的. 一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的 ...

  6. 使用queue 做一个分布式爬虫(一)

    这个作为调配的 taskMaster.py #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/12/23 15:21 # @au ...

  7. 线上CUP负载过高排查方法

      1.top命令查看线程占据的CPU 注意:上面行的cpu是多个内核的平均CPU,不可能超过100% 下面的cpu是每个进程实际占用的cpu,可能超过100% 备注:查看多个内核cpu,只需要在输入 ...

  8. three.js 制作一个三维的推箱子游戏

    今天郭先生发现大家更喜欢看我发的three.js小作品,今天我就发一个3d版本推箱子的游戏,其实webGL有很多框架,three.js并不合适做游戏引擎,但是可以尝试一些小游戏.在线案例请点击博客原文 ...

  9. 部分浏览器 set-cookie 不成功踩坑记录

    事件起因: 公司正在做一个sso的单点登录的项目,做完之后,在测试阶段,不同的终端的兼容测试时候,好几个不同的浏览器出现了不同的问题,有登录之后自动退出,有登陆不成功等问题. 在 pc 端只有 uc ...

  10. AC自动机&后缀自动机

    理解的不够深 故只能以此来加深理解 .我这个人就是蠢没办法 学长讲的题全程蒙蔽.可能我字符串就是菜吧,哦不我这个人就是菜吧. AC自动机的名字 AC 取自一个大牛 而自动机就比较有讲究了 不是寻常的东 ...