一個簡單的爬蟲程序主要分爲兩部分:
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.csdn.net";//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首頁看到的推薦的文章還不一樣呢,應該是後臺有什麼算法根據這個參數值得到了對應的的數據,這裏就不深究了,有興趣的小夥伴可以一起探討一下,歡迎評論區留言~~~~~~