前言
網絡爬蟲,是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。爬蟲可以通過模擬瀏覽器訪問網頁,從而獲取數據,一般網頁里會有很多個URL,爬蟲可以訪問這些URL到達其他網頁,相當于形成了一種數據結構——圖,我們通過廣度優先搜索和深度優先搜索的方式來遍歷這個圖,從而做到不斷爬取數據的目的。最近準備做一個電商網站,商品的原型就打算從一些電商網站上爬取,這里使用了HttpClient和Jsoup實現了一個簡答的爬取商品的demo,采用了多線程的方式,并將爬取的數據持久化到了數據庫。
項目環境搭建
整體使用技術
我IDE使用了Spring Tool Suite(sts),你也可以使用Eclipse或者是IDEA,安利使用IDEA,真的好用,誰用誰知道。
整個項目使用Maven進行構建嗎,使用Springboot進行自動裝配,使用HttpClient對網頁進行抓取,Jsoup對網頁進行解析,數據庫連接池使用Druild,還使用了工具類Guava和Commons.lang3。
項目結構
在sts里面新建一個maven工程,創建如下的包
- common 一些通用工具類
- constant 系統常量
- dao 數據庫訪問層
- service 服務層
- handler 調度控制層
- entity 實體層
這樣分層的意義是使得項目結構層次清晰,每層都有著其對應的職責,便于擴展和維護
pom文件
這里使用maven進行構建,還沒有了解maven的童鞋自行去了解,使用maven的好處是不用自己導入jar包和完整的生命周期控制,注意,使用阿里云的鏡像速度回加快很多。項目的pom.xml
文件如下
pom.xml
<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>
<groupId>com.exmaple</groupId>
<artifactId>spider-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spider-demo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<jsoup.version>1.10.3</jsoup.version>
<guava.version>22.0</guava.version>
<lang3.version>3.6</lang3.version>
<mysql.version>5.1.42</mysql.version>
<druid.version>1.1.0</druid.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<!-- guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.34</version>
</dependency>
</dependencies>
<build>
<finalName>spider-demo</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml文件
spring boot的配置文件有兩種形式,放在src/main/resources
目錄下,分別是application.yml
和application.properties
這里為了配置更加簡潔,使用了application.yml
作為我們的配置文件
application.yml
# mysql
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spider?useUnicode=true&characterEncoding=UTF-8&&useSSL=true
username: root
password: 123
這里可以在url,username和pssword里換成自己環境對應的配置
sql文件
這里我們創建了一個數據庫和一張表,以便后面將商品信息持久化到數據庫
db.sql
USE spider;
CREATE TABLE `goods_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`goods_id` VARCHAR(255) NOT NULL COMMENT '商品ID',
`goods_name` VARCHAR(255) NOT NULL COMMENT '商品名稱',
`img_url` VARCHAR(255) NOT NULL COMMENT '商品圖片地址',
`goods_price` VARCHAR(255) NOT NULL COMMENT '商品標價',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='商品信息表';
網頁的分析
網址URL的分析
我們要爬取的網頁的URL的基本地址是https://search.jd.com/Search
我們打開這個網頁,在搜索框內搜索零食,我們看一下我們的瀏覽器的地址欄的URL的變化,發現瀏覽器的地址欄變成了https://search.jd.com/Search?keyword=零食&enc=utf-8&wq=零食&pvid=2c636c9dc26c4e6e88e0dea0357b81a3
我們就可以對參數進行分析,keyword
和wq
應該是代表要搜索的關鍵字,enc
代表的編碼,pvid
不知道是什么,我們把這個參數去掉看能不能訪問https://search.jd.com/Search?keyword=零食&enc=utf-8&wq=零食
,發現這個URL也是可以正常訪問到這個網址的,那么我們就可以暫時忽略這個參數,參數就設置就設置keyword
,wq
和enc
這里我們要設置的參數就是
- keyword 零食
- wq 零食
- enc utf-8
網頁內容的分析
我們打開我們要爬取數據的頁面
使用瀏覽器-檢查元素
通過查看源碼,我們發現JD的商品列表放在id是J_goodsList的div下的的class是gl-warp clearfix的ul標簽下的class是gl-item的li標簽下
再分別審查各個元素,我們發現
- li標簽的data-sku的屬性值就是商品的ID
- li標簽下的class為p-name p-name-type-2的em的值就是商品的名稱
- li標簽下的class為p-price的strong標簽下的i標簽的值是商品的價格
- li標簽下的class為p-img的img標簽的src值就是商品的圖片URL
對網頁進行了分析以后,我們就可以通過對DOM結點的選擇來篩選我們想要的數據了
代碼的編寫
這里我們封裝了HttpClientUtils作為我們的工具類,以便以后使用
HttpClientUtils工具類
HttpClient.java
package com.exmaple.spider.common;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.exmaple.spider.constant.SysConstant;
/**
* HttpClient工具類
*
* @author ZGJ
* @date 2017年7月14日
*/
public class HttpClientUtils {
private final static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
private final static String GET_METHOD = "GET";
private final static String POST_METHOD = "POST";
/**
* GET請求
*
* @param url
* 請求url
* @param headers
* 頭部
* @param params
* 參數
* @return
*/
public static String sendGet(String url, Map<String, String> headers, Map<String, String> params) {
// 創建HttpClient對象
CloseableHttpClient client = HttpClients.createDefault();
StringBuilder reqUrl = new StringBuilder(url);
String result = "";
/*
* 設置param參數
*/
if (params != null && params.size() > 0) {
reqUrl.append("?");
for (Entry<String, String> param : params.entrySet()) {
reqUrl.append(param.getKey() + "=" + param.getValue() + "&");
}
url = reqUrl.subSequence(0, reqUrl.length() - 1).toString();
}
logger.debug("[url:" + url + ",method:" + GET_METHOD + "]");
HttpGet httpGet = new HttpGet(url);
/**
* 設置頭部
*/
logger.debug("Header\n");
if (headers != null && headers.size() > 0) {
for (Entry<String, String> header : headers.entrySet()) {
httpGet.addHeader(header.getKey(), header.getValue());
logger.debug(header.getKey() + " : " + header.getValue());
}
}
CloseableHttpResponse response = null;
try {
response = client.execute(httpGet);
/**
* 請求成功
*/
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, SysConstant.DEFAULT_CHARSET);
}
} catch (IOException e) {
logger.error("網絡請求出錯,請檢查原因");
} finally {
// 關閉資源
try {
if (response != null) {
response.close();
}
client.close();
} catch (IOException e) {
logger.error("網絡關閉錯誤錯,請檢查原因");
}
}
return result;
}
/**
* POST請求
*
* @param url
* 請求url
* @param headers
* 頭部
* @param params
* 參數
* @return
*/
public static String sendPost(String url, Map<String, String> headers, Map<String, String> params) {
CloseableHttpClient client = HttpClients.createDefault();
String result = "";
HttpPost httpPost = new HttpPost(url);
/**
* 設置參數
*/
if (params != null && params.size() > 0) {
List<NameValuePair> paramList = new ArrayList<>();
for (Entry<String, String> param : params.entrySet()) {
paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
logger.debug("[url: " + url + ",method: " + POST_METHOD + "]");
// 模擬表單提交
try {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, SysConstant.DEFAULT_CHARSET);
httpPost.setEntity(entity);
} catch (UnsupportedEncodingException e) {
logger.error("不支持的編碼");
}
/**
* 設置頭部
*/
if (headers != null && headers.size() > 0) {
logger.debug("Header\n");
if (headers != null && headers.size() > 0) {
for (Entry<String, String> header : headers.entrySet()) {
httpPost.addHeader(header.getKey(), header.getValue());
logger.debug(header.getKey() + " : " + header.getValue());
}
}
}
CloseableHttpResponse response = null;
try {
response = client.execute(httpPost);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, SysConstant.DEFAULT_CHARSET);
} catch (IOException e) {
logger.error("網絡請求出錯,請檢查原因");
} finally {
try {
if (response != null) {
response.close();
}
client.close();
} catch (IOException e) {
logger.error("網絡關閉錯誤");
}
}
}
return result;
}
/**
* post請求發送json
* @param url
* @param json
* @param headers
* @return
*/
public static String senPostJson(String url, String json, Map<String, String> headers) {
CloseableHttpClient client = HttpClients.createDefault();
String result = "";
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(stringEntity);
logger.debug("[url: " + url + ",method: " + POST_METHOD + ", json: " + json + "]");
/**
* 設置頭部
*/
if (headers != null && headers.size() > 0) {
logger.debug("Header\n");
if (headers != null && headers.size() > 0) {
for (Entry<String, String> header : headers.entrySet()) {
httpPost.addHeader(header.getKey(), header.getValue());
logger.debug(header.getKey() + " : " + header.getValue());
}
}
}
CloseableHttpResponse response = null;
try {
response = client.execute(httpPost);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, SysConstant.DEFAULT_CHARSET);
} catch (IOException e) {
logger.error("網絡請求出錯,請檢查原因");
} finally {
try {
if (response != null) {
response.close();
}
client.close();
} catch (IOException e) {
logger.error("網絡關閉錯誤");
}
}
return result;
}
}
SyConstant.java 系統常量
SysConstant.java
package com.exmaple.spider.constant;
/**
* 系統全局常量
* @author ZGJ
* @date 2017年7月15日
*/
public interface SysConstant {
/**
* 系統默認字符集
*/
String DEFAULT_CHARSET = "utf-8";
/**
* 需要爬取的網站
*/
String BASE_URL = "https://search.jd.com/Search";
interface Header {
String ACCEPT = "Accept";
String ACCEPT_ENCODING = "Accept-Encoding";
String ACCEPT_LANGUAGE = "Accept-Language";
String CACHE_CONTROL = "Cache-Controle";
String COOKIE = "Cookie";
String HOST = "Host";
String PROXY_CONNECTION = "Proxy-Connection";
String REFERER = "Referer";
String USER_AGENT = "User-Agent";
}
/**
* 默認日期格式
*/
String DEFAULT_DATE_FORMAT = "yyy-MM-dd HH:mm:ss";
}
GoodsInfo 商品信息
GoodsInfo.java
package com.exmaple.spider.entity;
public class GoodsInfo {
private Integer id;
private String goodsId;
private String goodsName;
private String imgUrl;
private String goodsPrice;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getGoodsId() {
return goodsId;
}
public void setGoodsId(String goodsId) {
this.goodsId = goodsId;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getGoodsPrice() {
return goodsPrice;
}
public void setGoodsPrice(String goodsPrice) {
this.goodsPrice = goodsPrice;
}
public GoodsInfo(String goodsId, String goodsName, String imgUrl, String goodsPrice) {
super();
this.goodsId = goodsId;
this.goodsName = goodsName;
this.imgUrl = imgUrl;
this.goodsPrice = goodsPrice;
}
}
GoodsInfoDao 商品信息Dao層
因為這里僅僅涉及到把商品信息寫入到數據庫比較簡單的操作,并沒有使用MyBatis或者Hibernate框架,只是使用了Spring的JdbcTemplate對數據進行插入操作
GoodsInfoDao.java
package com.exmaple.spider.dao;
import java.util.List;
import com.exmaple.spider.entity.GoodsInfo;
/**
* 商品Dao層
* @author ZGJ
* @date 2017年7月15日
*/
public interface GoodsInfoDao {
/**
* 插入商品信息
* @param infos
*/
void saveBatch(List<GoodsInfo> infos);
}
GoodsInfoDaoImpl.java
package com.exmaple.spider.dao.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.exmaple.spider.dao.GoodsInfoDao;
import com.exmaple.spider.entity.GoodsInfo;
@Repository
public class GoodsInfoDaoImpl implements GoodsInfoDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveBatch(List<GoodsInfo> infos) {
String sql = "REPLACE INTO goods_info(" + "goods_id," + "goods_name," + "goods_price," + "img_url) "
+ "VALUES(?,?,?,?)";
for(GoodsInfo info : infos) {
jdbcTemplate.update(sql, info.getGoodsId(), info.getGoodsName(), info.getGoodsPrice(), info.getImgUrl());
}
}
}
商品的Dao層實現了向數據庫里插入商品信息,使用JdbcTemplate和占位符的方式設置sql語句
SpiderService 爬蟲服務層
SpiderService.java
package com.exmaple.spider.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.exmaple.spider.common.HttpClientUtils;
import com.exmaple.spider.constant.SysConstant;
import com.exmaple.spider.dao.GoodsInfoDao;
import com.exmaple.spider.entity.GoodsInfo;
import com.google.common.collect.Lists;
@Service
public class SpiderService {
private static Logger logger = LoggerFactory.getLogger(SpiderService.class);
@Autowired
private GoodsInfoDao goodsInfoDao;
private static String HTTPS_PROTOCOL = "https:";
public void spiderData(String url, Map<String, String> params) {
String html = HttpClientUtils.sendGet(url, null, params);
if(!StringUtils.isBlank(html)) {
List<GoodsInfo> goodsInfos =parseHtml(html);
goodsInfoDao.saveBatch(goodsInfos);
}
}
/**
* 解析html
* @param html
*/
private List<GoodsInfo> parseHtml(String html) {
//商品集合
List<GoodsInfo> goods = Lists.newArrayList();
/**
* 獲取dom并解析
*/
Document document = Jsoup.parse(html);
Elements elements = document.
select("ul[class=gl-warp clearfix]").select("li[class=gl-item]");
int index = 0;
for(Element element : elements) {
String goodsId = element.attr("data-sku");
String goodsName = element.select("div[class=p-name p-name-type-2]").select("em").text();
String goodsPrice = element.select("div[class=p-price]").select("strong").select("i").text();
String imgUrl = HTTPS_PROTOCOL + element.select("div[class=p-img]").select("a").select("img").attr("src");
GoodsInfo goodsInfo = new GoodsInfo(goodsId, goodsName, imgUrl, goodsPrice);
goods.add(goodsInfo);
String jsonStr = JSON.toJSONString(goodsInfo);
logger.info("成功爬取【" + goodsName + "】的基本信息 ");
logger.info(jsonStr);
if(index ++ == 9) {
break;
}
}
return goods;
}
}
Service層通過使用HttpClientUtils模擬瀏覽器訪問頁面,然后再使用Jsoup對頁面進行解析,Jsoup的使用和Jquery的DOM結點選取基本相似,可以看作是java版的Jquery,如果寫過Jquery的人基本上就可以看出是什么意思。
每抓取一條信息就會打印一次記錄,而且使用fastjson將對象轉換成json字符串并輸出
在寫測試代碼的時候發現,發現爬取的數據只有前10條是完整的,后面的爬取的有些是不完整的,按道理來說是對于整個頁面都是通用的,就是不知道為什么只有前面才是完整的,排查了很久沒用發現原因,這里就只選擇了前面的10條作為要爬取的數據
我們了解到,我們要爬取數據前要分析我們要爬取的數據有哪些,再分析網友的結構,然后對網頁進行解析,選取對應的DOM或者使用正則表達式篩選,思路首先要清晰,有了思路之后剩下的也只是把你的思路翻譯成代碼而已了。
SpiderHandler 爬蟲調度處理器
SpiderHandler.java
package com.exmaple.spider.handler;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.exmaple.spider.constant.SysConstant;
import com.exmaple.spider.service.SpiderService;
import com.google.common.collect.Maps;
/**
* 爬蟲調度處理器
* @author ZGJ
* @date 2017年7月15日
*/
@Component
public class SpiderHandler {
@Autowired
private SpiderService spiderService;
private static final Logger logger = LoggerFactory.getLogger(SpiderHandler.class);
public void spiderData() {
logger.info("爬蟲開始....");
Date startDate = new Date();
// 使用現線程池提交任務
ExecutorService executorService = Executors.newFixedThreadPool(5);
//引入countDownLatch進行線程同步,使主線程等待線程池的所有任務結束,便于計時
CountDownLatch countDownLatch = new CountDownLatch(100);
for(int i = 1; i < 201; i += 2) {
Map<String, String> params = Maps.newHashMap();
params.put("keyword", "零食");
params.put("enc", "utf-8");
params.put("wc", "零食");
params.put("page", i + "");
executorService.submit(() -> {
spiderService.spiderData(SysConstant.BASE_URL, params);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
Date endDate = new Date();
FastDateFormat fdf = FastDateFormat.getInstance(SysConstant.DEFAULT_DATE_FORMAT);
logger.info("爬蟲結束....");
logger.info("[開始時間:" + fdf.format(startDate) + ",結束時間:" + fdf.format(endDate) + ",耗時:"
+ (endDate.getTime() - startDate.getTime()) + "ms]");
}
}
SpiderHandelr作為一個爬蟲服務調度處理器,這里采用了ExecutorService
線程池創建了5個線程進行多線程爬取,我們通過翻頁發現,翻頁過后地址URL多了一個page
參數,而且這個參數還只能是奇數才有效,也就是page
為1,3,5,7……代表第1,2,3,4……頁。這里就只爬了100頁,每頁10條數據,將page
作為不同的參數傳給不同的任務。
這里我想統計一下整個爬取任務所用的時間,假如不使用同步工具類的話,因為任務是分到線程池中去運行的,而主線程會繼續執行下去,主線程和線程池中的線程是獨立運行的,主線程會提前結束,所以就無法統計時間。
這里我們使用CountDownLatch同步工具類,它允許一個或多個線程一直等待,直到其他線程的操作執行完后再執行。也就是說可以讓主線程等待線程池內的線程執行結束再繼續執行,里面維護了一個計數器,開始的時候構造計數器的初始數量,每個線程執行結束的時候調用countdown()方法,計數器就減1,調用await()方法,假如計數器不為0就會阻塞,假如計數器為0了就可以繼續往下執行
executorService.submit(() -> {
spiderService.spiderData(SysConstant.BASE_URL, params);
countDownLatch.countDown();
});
這里使用了Java8中的lambda表達式替代了匿名內部類,詳細的可以自行去了解
這里還可以根據自己的業務需求做一些代碼的調整和優化,比如實現定時任務爬取等等
App.java Spring Boot啟動類
App.java
package com.exmaple.spider;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.exmaple.spider.handler.SpiderHandler;
@SpringBootApplication
public class App {
@Autowired
private SpiderHandler spiderHandler;
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
@PostConstruct
public void task() {
spiderHandler.spiderData();
}
}
使用@PostConstruct
注解會在spring容器實例化bean之前執行這個方法
運行結果
我們以Spring Boot App的方式運行App.java文件,得到的結果如下:
我們在看一下數據庫內的信息
發現數據庫也有信息了,大功告成
總結
寫一個簡單的爬蟲其實也不難,但是其中也有不少的知識點需要梳理和記憶,發現問題或者是錯誤,查google,查文檔,一點點debug去調試,最終把問題一點點的解決,編程其實需要是解決問題的能力,這種的能力的鍛煉需要我們去多寫代碼,寫完了代碼之后還要多思考,思考為什么要這樣寫?還有沒有更好的實現方式?為什么會出問題?需要怎么解決?這才是一名優秀的程序員應該養成的習慣,共勉!
個人博客: http://blog.zgj12138.cn
CSDN: http://blog.csdn.net/zgj12138