Java資料採集:Xpath解析 + WebMagic案例:採集豆瓣豆列電影資訊儲存MySql資料庫
Maven的安裝與設定環境變數
2. 設定環境變數
- 新建變數
MAVEN_HOME
,值為Maven的目錄X:\XXX\apache-maven-XXX
- 將
%MAVEN_HOME%\bin
新增到Path
變數下
3. 檢測:執行CMD,輸入mvn -v
後可以看到Maven的版本資訊等則表示安裝成功
IDE中安裝Maven外掛
以Eclipse為例:有些教程中會修改倉庫的位置,建議不要修改,使用預設.m2資料夾就可以,不然需要更改很多配置,容易出錯不易排查。
WebMagic
WebMagic是一個強大的java爬蟲框架,它的結構分為Downloader、PageProcessor、Scheduler、Pipeline四大元件,並由Spider將它們彼此組織起來。這四大元件對應爬蟲生命週期中的下載、處理、管理和持久化等功能。WebMagic的設計參考了Scapy,但是實現方式更Java化一些。
而Spider則將這幾個元件組織起來,讓它們可以互相互動,流程化的執行,可以認為Spider是一個大的容器,它也是WebMagic邏輯的核心。
WebMagic總體架構圖如下:
元件講解:
1. Downloader
Downloader負責從網際網路上下載頁面,以便後續處理。WebMagic預設使用了Apache HttpClient作為下載工具。
2. PageProcessor
PageProcessor負責解析頁面,抽取有用資訊,以及發現新的連結。WebMagic使用Jsoup作為HTML解析工具,並基於其開發瞭解析XPath的工具Xsoup。
在這四個元件中,PageProcessor對於每個站點每個頁面都不一樣,是需要使用者定製的部分。
3. Scheduler
Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic預設提供了JDK的記憶體佇列來管理URL,並用集合來進行去重。也支援使用Redis進行分散式管理。
除非專案有一些特殊的分散式需求,否則無需自己定製Scheduler。
4. Pipeline
Pipeline負責抽取結果的處理,包括計算、持久化到檔案、資料庫等。WebMagic預設提供了“輸出到控制檯”和“儲存到檔案”兩種結果處理方案。
Pipeline定義了結果儲存的方式,如果你要儲存到指定資料庫,則需要編寫對應的Pipeline。對於一類需求一般只需編寫一個Pipeline。
process方法的引數page就是下載的整個html頁面,這一部分內容WebMagic框架已經幫我們實現了,我們只需要通過XPath、正則表示式和CSS選擇器三種中的任意一種抽取技術獲取頁面內容即可,常用的方法介紹:
- xpath(String xpath)使用XPath選擇 : html.xpath("//div[@class='title']")
- $(String selector)使用Css選擇器選擇 html.$("div.title")
- $(String selector,String attr)使用Css選擇器選擇 html.$("div.title","text")
- css(String selector) 功能同$(),使用Css選擇器選擇 html.css("div.title")
- links() 選擇所有連結 html.links()
- get()返回一條String型別的結果 String link= html.links().get()
- match()是否有匹配結果 if (html.links().match()){ xxx; }
- all() 返回所有抽取結果,返回一個集合 List links= html.links().all()
- toString() 功能同get(),返回一條String型別的結果 String link= html.links().toString()
案例:
WebMagic採集日劇電影名字和網址及評分、導演、年份等電影資訊儲存在mysql資料庫
1. 建立SQL資料庫
使用Navicat建立表movies。注:設定m_id自增
2. 新建mavent project
選擇如下archetypes:
- Group ID:org.apache.maven.archetypes
- Artifact ID:maven-archetype-quickstart
填寫Group Id,Artfact Id等資訊:
- Group Id:com.liudm
- Artfact Id:WebMagicMovie
- Package:com.liudm.WebMagicMovie
專案結構如圖:
3. 開啟pom.xml檔案,新增Maven依賴
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.3</version>
</dependency>
新建lib資料夾,新增jar包,右鍵 -->Build Path-->Add to Build Path,選擇要重新整理的maven專案,右鍵-->Maven-->Update Project。
4. 編寫程式碼
4.1 建立爬取網頁的類:在com.liudm.WebMagicMovie包中建立類檔案,命名為WebMagicMovieGetdata,編寫爬取豆瓣電影網頁中電影資訊的程式碼。
第一部分:關於爬蟲的配置,包括編碼、抓取間隔、超時時間、重試次數等,也包括一些模擬的引數,例如User Agent、cookie,以及代理的設定等。
private Site site = Site.me()
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0")
.setRetryTimes(3)
.setSleepTime(1000);
public Site getSite() {
return site;
}
第二部分:爬蟲的核心部分:對於下載到的Html頁面,WebMagic裡主要使用了三種抽取技術:XPath、正則表示式和CSS選擇器,這部分內容在process方法中定義即可。
public void process(Page page) {
//這段程式碼使用了XPath,它的意思是“查詢所有class屬性為'title'的div元素,並找到他的a子節點,並提取a節點的文字資訊
List<String> titles = page.getHtml().xpath("//div[@class='title']/a/text()").all(); //這是匹配電影名稱
List<String> movieUrls = page.getHtml().xpath("//div[@class='post']/a").links().all(); //這是匹配電影url
List<String> ratings = page.getHtml().xpath("//div[@class='rating']/span[@class='rating_nums']/text()").all(); //這是匹配評分
List<String> attrFields = page.getHtml().xpath("//div[@class='abstract']").all();
}
第三部分:解決url問題,一個站點的頁面是很多的,一開始我們不可能全部列舉出來,於是如何發現後續的連結,是一個爬蟲不可缺少的一部分。
/* 獲得下一頁內容
* 第一頁:https://www.douban.com/doulist/3907668/
* 第二頁:https://www.douban.com/doulist/3907668/?start=25&sort=seq&sub_type=
* 第三頁:https://www.douban.com/doulist/3907668/?start=50&sort=seq&sub_type=
*/
page.addTargetRequests(page.getHtml()
.links()
.regex("https://www.douban.com/doulist/3907668/\\?start=\\d+&sort=seq&sub_type=").all());
//採集所有頁面
//這段程式碼的分為兩部分,page.getHtml()....all()用於獲取所有滿足"https://www....type="這個正則表示式的連結;
//page.addTargetRequests()則將這些連結加入到待抓取的佇列中去。
第四部分:我們將獲取的資料儲存在MySql資料庫,首先將資料通過putField方法攜帶,page.putField("titles", titles);本質設定到了ResultItems物件裡了。
page.putField("titles", titles);
page.putField("movieUrls", movieUrls);
page.putField("ratings", ratings);
page.putField("attrFields", attrFields);
第五部分:啟動執行緒,開始爬蟲
public static void main(String[] args) throws IOException {
Spider.create(new WebMagicMovieGetdata())
//從"https://www.douban.com/doulist/3907668/"開始抓取
.addUrl("https://www.douban.com/doulist/3907668/")
//儲存在資料庫中
.addPipeline(new MySqlPipeline())
//開啟5個執行緒抓取
.thread(5)
//啟動爬蟲
.run();
}
4.2 在src/main/java下建立類檔案,命名為Movie,包名為com.liudm.entity,利用面向物件的思想將電影資訊封裝:
在資料儲存時,我們new了一個MySqlPipeline類物件,MySqlPipeline是我們自己定義的,Pipeline負責抽取結果的處理,包括計算、持久化到檔案、資料庫等。WebMagic預設提供了“輸出到控制檯”和“儲存到檔案”兩種結果處理方案。Pipeline定義了結果儲存的方式,如果你要儲存到指定資料庫,則需要編寫對應的Pipeline。對於一類需求一般只需編寫一個Pipeline,所以接下來我們定義資料庫儲存相關內容。
package com.liudm.entity;
public class Movie {
private int m_id;
private String m_title;
private String m_urls;
private float m_ratings;
private String m_dir;
private String m_actor;
private String m_type;
private String m_time;
public Movie() {
super();
// TODO Auto-generated constructor stub
}
public Movie(String m_title, String m_urls, float m_ratings, String m_dir,
String m_actor, String m_type, String m_time) {
super();
this.m_title = m_title;
this.m_urls = m_urls;
this.m_ratings = m_ratings;
this.m_dir = m_dir;
this.m_actor = m_actor;
this.m_type = m_type;
this.m_time = m_time;
}
public Movie(int m_id, String m_title, String m_urls, float m_ratings,
String m_dir, String m_actor, String m_type, String m_time) {
super();
this.m_id = m_id;
this.m_title = m_title;
this.m_urls = m_urls;
this.m_ratings = m_ratings;
this.m_dir = m_dir;
this.m_actor = m_actor;
this.m_type = m_type;
this.m_time = m_time;
}
public int getM_id() {
return m_id;
}
public void setM_id(int m_id) {
this.m_id = m_id;
}
public String getM_title() {
return m_title;
}
public void setM_title(String m_title) {
this.m_title = m_title;
}
public String getM_urls() {
return m_urls;
}
public void setM_urls(String m_urls) {
this.m_urls = m_urls;
}
public float getM_ratings() {
return m_ratings;
}
public void setM_ratings(float m_ratings) {
this.m_ratings = m_ratings;
}
public String getM_dir() {
return m_dir;
}
public void setM_dir(String m_dir) {
this.m_dir = m_dir;
}
public String getM_actor() {
return m_actor;
}
public void setM_actor(String m_actor) {
this.m_actor = m_actor;
}
public String getM_type() {
return m_type;
}
public void setM_type(String m_type) {
this.m_type = m_type;
}
public String getM_time() {
return m_time;
}
public void setM_time(String m_time) {
this.m_time = m_time;
}
@Override
public String toString() {
return "Movie [m_id=" + m_id + ", m_title=" + m_title + ", m_urls="
+ m_urls + ", m_ratings=" + m_ratings + ", m_dir=" + m_dir
+ ", m_actor=" + m_actor + ", m_type=" + m_type + ", m_time="
+ m_time + "]";
}
}
4.3 在src/main/java下建立介面檔案,命名為MovieDao,包名為com.liudm.moviedao,在編寫新增資料的抽象方法:
package com.liudm.moviedao;
import com.liudm.entity.Movie;
public interface MovieDao {
public void addMovie(Movie movie);
}
4.4 在src/main/java下建立類檔案,命名為MovieDaoImpl,包名為com.liudm.moviedao,讓該類實現MovieDao介面,重寫addMovie方法:
package com.liudm.moviedao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.liudm.entity.Movie;
public class MovieDaoImpl implements MovieDao{
@Override
public void addMovie(Movie movie) {
// TODO Auto-generated method stub
Connection con = null;
PreparedStatement pre = null;
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/movies?useUnicode=true&characterEncoding=UTF-8","root","nothing");
String sql = "insert into movies(m_title,m_urls,m_ratings,m_dir,m_actor,m_type,m_time) values (?,?,?,?,?,?,?);";
pre = con.prepareStatement(sql);
pre.setString(1,movie.getM_title());
pre.setString(2,movie.getM_urls());
pre.setFloat(3,movie.getM_ratings());
pre.setString(4,movie.getM_dir());
pre.setString(5,movie.getM_actor());
pre.setString(6,movie.getM_type());
pre.setString(7,movie.getM_time());
pre.execute();
System.out.println("新增成功!");
} catch (ClassNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
} finally {
try {
pre.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
try {
con.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
4.5 在src/main/java下的包com.zy.WebMagicMovie中建立類檔案,命名為MySqlPipeline,編寫程式碼,將採集的每個網站的首頁分別儲存到MySql中。MySqlPipeline類實現Pipeline介面,重寫process方法,process方法中的resultItems引數即為爬蟲時page.putField("attrFields", attrFields);中的資料,只需要通過鍵獲取值,迴圈遍歷儲存到資料庫即可。
package com.liudm.WebMagicMovie;
import java.util.List;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
import com.liudm.entity.Movie;
import com.liudm.moviedao.MovieDao;
import com.liudm.moviedao.MovieDaoImpl;
public class MySqlPipeline implements Pipeline {
@Override
public void process(ResultItems resultItems, Task task) {
// TODO Auto-generated method stub
Movie movie = new Movie();
List<String> titles = resultItems.get("titles");
List<String> movieUrls = resultItems.get("movieUrls");
List<String> ratings = resultItems.get("ratings");
List<String> attrFields = resultItems.get("attrFields");
for (int i = 0; i < titles.size(); i++) {
movie.setM_title(titles.get(i));
movie.setM_urls(movieUrls.get(i));
movie.setM_ratings(Float.valueOf(ratings.get(i)));
String [] movieAttrs = attrFields.get(i).split("<br>");
if (movieAttrs.length > 4) {
movie.setM_dir(movieAttrs[0].substring(23).trim());
movie.setM_actor(movieAttrs[1].trim());
movie.setM_type(movieAttrs[2].substring(4).trim());
movie.setM_time(movieAttrs[4].substring(4,9).trim());
} else {
movie.setM_dir("不詳");
movie.setM_actor("不詳");
movie.setM_type("不詳");
movie.setM_time("不詳");
}
MovieDao movieDao = new MovieDaoImpl();
movieDao.addMovie(movie);
}
}
}
PS:WebMagicMovieGetdata類的完整程式碼:
package com.liudm.WebMagicMovie;
import java.io.IOException;
import java.util.List;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
public class WebMagicMovieGetdata implements PageProcessor {
private Site site = Site.me()
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0")
.setRetryTimes(3)
.setSleepTime(1000);
public Site getSite(){
return site;
}
public void process(Page page){
List<String> titles = page.getHtml().xpath("//div[@class='title']/a/text()").all();
List<String> movieUrls = page.getHtml().xpath("//div[@class='post']/a").links().all();
List<String> ratings = page.getHtml().xpath("//div[@class='rating']/span[@class='rating_nums']/text()").all();
List<String> attrFields = page.getHtml().xpath("//div[@class='abstract']").all();
// 測試能否獲取到資料
// for (int i = 0; i < titles.size(); i++) {
// System.out.println(titles.get(i));
// System.out.println(movieUrls.get(i));
// System.out.println(retings.get(i));
// System.out.println(attrFields.get(i));
// }
page.putField("titles", titles);
page.putField("movieUrls", movieUrls);
page.putField("ratings", ratings);
page.putField("attrFields", attrFields);
// page.addTargetRequests(page.getHtml().links().regex("https://www.douban.com/doulist/3907668/\\?start=\\d+&sort=seq&sub_type=").all());
}
public static void main(String[] args) throws IOException {
Spider.create(new WebMagicMovieGetdata())
.addUrl("https://www.douban.com/doulist/3907668/")
.addPipeline(new MySqlPipeline())
.thread(5)
.run();
}
}
5. 執行執行WebMagicMovieGetdata.java檔案,控制檯輸入資訊如下:
6. 開啟Navivat,重新整理movies表,查看錶資訊,可以看到獲取的資料已經存入資料庫:
異常1:java.sql.SQLException: Field 'm_id' doesn't have a default value
解決辦法:資料庫中設定m_id自增