一个简单的爬虫程序主要分为两部分:
1、抓取网站资源,也就是我们通过浏览器看到的页面资源(HTML源码)。
2、制定筛选规则,筛选出我们想要的数据。
这里就以爬取csdn首页的文章信息为例实现一个简单的Java爬虫。我这里是个spring boot项目,jdk版本1.8。不得不说新版eclipse自带maven,自己再安装个STS组件构建srping boot项目简直方便快捷。话不多说直接放代码。
这里是我的pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>sqlTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sqlTest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
既然是spring boot项目 下面就是properties文件(最基本的配置,就不写注解了):
spring.datasource.url=jdbc:mysql://localhost:3306/myTest?useUnicode=true&characterEncoding=utf8
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
#spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
server.port=8084
server.servlet.context-path=/test
下面双手奉上实现类:
package com.example.demo;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.http.HttpServletRequest;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.bean.PUser;
import com.example.demo.dao.UserDao;
@Controller
public class TestController {
@Autowired(required = true)
private UserDao userDao;
@Autowired
private EntityManager em;
private Query query;
@Transactional
@RequestMapping("/hello1") //该方法的访问路径
public String test(Model model, HttpServletRequest request) {
String url1 = "https://www.youkuaiyun.com";//csdn首页的地址
crawler(url1);//开始爬虫
System.out.println("爬虫结束");
return "index.html";
}
//爬取csdn首页的文章信息
private void crawler(String urlString) {
try {
// 文章名称
String regex = ";}\">\\s[\\S\\s]+\\s<\\/a>\\s<\\/h2";
// regex = "[\\u4e00-\\u9fa5]+";
Pattern pattern = Pattern.compile(regex);
//作者
regex = ">\\s[\\u4e00-\\u9fa5_a-zA-Z0-9\\s,!“”?()\\|-]+\\s<\\/a";
Pattern pattern2 = Pattern.compile(regex);
Document document = Jsoup.connect(urlString).get();//通过url直接抓取网页的全部源码
//把上面的document复制到笔记本 会发现每个文章区域所在div的class属性为list_con
//Jsoup为我们封装了通过class筛选元素的方法 省的我们通过正则表达式自己筛选
Elements elements = document.getElementsByClass("list_con");
//因为首页有很多文章 所以需要遍历每个文章区域的源码
for (Element element : elements) {
if (element == null) {
continue;
}
//转换为String类型 方便筛选
String data = element.toString();
String essayName = "";//文章名称
String autherName = "";//作者
String url = "";//文章地址
String time = "";//时间
String readNum = "";//阅读数
String commonNum = "";//评论数
//上面得到的data数据还是包括很多html源码 接下来 我们就要从这一小堆源码中是,筛选出我们需要的信息
// 筛选出文章名称
Matcher matcher = pattern.matcher(data);
if (matcher.find()) {
essayName = matcher.group();
if(essayName.length()>20) {//这个正则可能需要匹配两次才能匹配到
Matcher matcher2 = pattern.matcher(essayName.substring(1));
if(matcher2.find()) {
essayName = matcher2.group();
}
}
essayName = essayName.substring(essayName.indexOf('>') + 2, essayName.indexOf('<') - 1);
System.out.println("文章名称:"+essayName);
}
//查看源码 发现在这个div里第一个href属性就是对应文章地址
int start = data.indexOf("href=\"");
int end = data.indexOf("target", start);
url = data.substring(start+6, end-2);
System.out.println("文章地址:"+url);
// 筛选出作者
start = data.indexOf("class=\"name\"");
end = data.indexOf("</dd>", start);
String data2 = data.substring(start, end);
matcher = pattern2.matcher(data2);
if (matcher.find()) {
autherName = matcher.group();
autherName = autherName.substring(autherName.indexOf('>') + 2, autherName.indexOf('<') - 1);
System.out.println("作者:"+autherName);
}
//筛选出时间
start = data.indexOf("class=\"time\"");
end = data.indexOf("</dd>", start);
time = data.substring(start+13, end);
time = time.replaceAll(" ", "");
time = time.replaceAll("[\t\n\r]", "");
System.out.println("时间:"+time);
if(autherName.indexOf("laogt1")>=0) {
System.out.println(data);
}
//筛选出阅读数
start = data.indexOf("class=\"num\"");
if(start>=0) {
end = data.indexOf("</span>", start);
readNum = data.substring(start+12, end);
}
System.out.println("阅读数:"+readNum);
//筛选出评论数
int start2 = data.indexOf("class=\"num\"", end);
if(start2>=0) {
int end2 = data.indexOf("</span>", start2);
commonNum = data.substring(start2+12, end2);
}
System.out.println("评论数:"+commonNum+"\n");
//插入数据库
String sql = "insert into csdn_essay (essay_name,url,auther_name,time,read_num,common_num) values ('"+essayName+"','"+url+"','"+autherName+"','"+time+"','"+readNum+"','"+commonNum+"')";
em.createNativeQuery(sql).executeUpdate();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上是爬虫的工作成果:
首页默认只能爬到38条数据 ,用浏览器访问可以看到,初次加载也是38条数据,但是每当滑到页面底部会自动继续加载10条数据。所以我们判断每次加载应该是传了什么参数来控制加载的数据,好奇心促使我用burp Suite拦截了以下请求包,结果发现:
前两个参数每次访问都是一样的,而第三个参数show_offset看名字就非常可疑,前10位应该是个时间按戳,后面的我也不知道是啥,在url里拼接了这个参数并且修改了一下参数值,发现得到的数据是不大一样,但是跟之前的对比有重复的。 想来用浏览器每次访问csdn首页看到的推荐的文章还不一样呢,应该是后台有什么算法根据这个参数值得到了对应的的数据,这里就不深究了,有兴趣的小伙伴可以一起探讨一下,欢迎评论区留言~~~~~~