1. 程式人生 > >Java資料採集:Xpath解析 + WebMagic案例:採集豆瓣豆列電影資訊儲存MySql資料庫

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自增