【Java爬蟲學習】WebMagic框架爬蟲學習實戰一:爬取網易雲歌單資訊,並存入mysql中
最近,需要使用Java進行爬蟲編寫,就去學了Java的爬蟲。因為之前學習了Scrapy框架,所以學Java的爬蟲使用了WebMagic框架,這個框架是基於Scrapy框架開發的。大家有興趣可以去看看操作文件:
這個框架是國人開發的,所以說明文件都是中文,簡單易懂。
匯入WebMagic框架的方法在操作文件中有,在這就不講述了(建議看這篇文章前,先去看完操作文件。我是匯入jar包使用)
我使用的版本是0.7.3。
webmagic的各個模組不用再重複說,就說程式碼實現(預設您看完了操作文件)
首先,建立一個Site物件,用於配置爬蟲,包括抓取間隔、重試次數,請求頭部等。
private Site site = Site.me().setSleepTime(1000).setRetryTimes(3).addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0");
然後編寫process方法。process方法是定製爬蟲邏輯的核心部分,在這裡制定篩選規則。
page.putField()方法可以將資料儲存為key:value的形式,然後交給pipeline檔案處理。
後續網站的url和第一頁的url相差最後一個數值,所以採用字串拼接就可以。
然後使用page.addTargetRequest()方法將後續url加入爬取序列中去。
(basic是在前面定義的靜態成員)
url="https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset="
public void process(Page page) { //將資料交給pipeline檔案處理 page.putField("name",page.getHtml().xpath("//li/p[@class=dec]/a/text()").all()); page.putField("src",page.getHtml().xpath("//li/p[@class=dec]/a/@href").all()); page.putField("clicknum",page.getHtml().xpath("//li//div[@class=bottom]/span[@class=nb]/text()").all()); page.putField("author",page.getHtml().xpath("//li//a[@class=nm]/text()").all()); page.putField("homepage",page.getHtml().xpath("//li//a[@class=nm]/@href").all()); //連續爬取後續網頁 int flag = 35; String nexturl = null; for (int i=flag; i<1436;){ nexturl = basic+i; page.addTargetRequest(nexturl); i+=35; } }
接下來在main函式中建立爬蟲,啟動三個執行緒進行爬取。
public static void main(String[] args){
Spider.create(new music163())
.addUrl(basic+"0")
.addPipeline(new music163Pipeline())
.thread(3)
.run();
}
將資料交給pipeline,在pipeline檔案中進行資料持久化,pipeline實現Pipeline介面
在pipeline檔案中,我使用List物件去存放需要持久化的內容。
items.get("key")方法用於從items物件中提取出對應key的值。
然後將歌單地址和作者主頁進行了url拼接
public void process(ResultItems items, Task t) {
List<String> list_name = new ArrayList<String>(); //歌單名稱
List<String> list_src = new ArrayList<String>(); //歌單地址
List<String> list_clicknum = new ArrayList<String>(); //歌單播放量
List<String> list_author = new ArrayList<String>(); //歌單作者
List<String> list_author_homepage = new ArrayList<String>();//作者主頁
//建立歌單地址和作者主頁地址的域 url
String basicsrc = "https://music.163.com";
//提取所有需要的資訊
list_name.addAll(items.get("name"));
list_src.addAll(items.get("src"));
list_clicknum.addAll(items.get("clicknum"));
list_author.addAll(items.get("author"));
list_author_homepage.addAll(items.get("homepage"));
int len=list_src.size();
for(int i=0; i<len; i++){
//修改歌單地址
list_src.set(i, basicsrc+list_src.get(i));
//修改作者個人主頁地址
list_author_homepage.set(i, basicsrc+list_author_homepage.get(i));
}
最後需要將資料持久化到mysql中。
將資料插入到資料庫中一般有四步:
- 載入資料庫驅動
- 連線資料庫
- 建立PrepareStatement物件,用於執行sql語句
- 關閉資源
下面的程式碼是根據這四步進行的,可以對照著看。
注意點:
1. DriverManager.getConnection() 方法的引數是url,user,password。
2. 批量處理:設定為手動提交,加入批量處理,進行批量處理,手動提交。
3. 將資料放入sql語句中,可以一次寫入一行,也可以一次將一列寫入。我這裡是使用迴圈,一次寫入一行。寫入的資訊與資料庫中對應位置的資料型別要相同。
4. 最後記得要關閉資源,資源關閉的順序。
//建立連線物件
Connection conn = null;
//載入資料庫驅動
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("載入驅動失敗!");
e.printStackTrace();
}
//連線資料庫
String url = "jdbc:mysql://localhost:3306/test"; //這是資料庫的位置
//進行連線
try {
conn = DriverManager.getConnection(url, "root", "lanqiyu0328");
//因為需要批量提交資料,所以設定conn的提交方式為手動
conn.setAutoCommit(false);
} catch (SQLException e) {
System.out.println("連線失敗!");
e.printStackTrace();
}
//建立PrepareStatement物件,用來執行sql語句
String sql = "insert ignore into music_list value(?,?,?,?,?)";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql); //建立PreparedStatement物件
//將資料一次放入sql語句裡面
int num = list_name.size();
for(int i=0; i<num; i++){
pstmt.setString(1, list_name.get(i));
pstmt.setString(2, list_src.get(i));
pstmt.setString(3, list_clicknum.get(i));
pstmt.setString(4, list_author.get(i));
pstmt.setString(5, list_author_homepage.get(i));
//將資料加入批量處理
pstmt.addBatch();
}
//批量處理資料之後手動提交
pstmt.executeBatch();
conn.commit();
} catch (SQLException e) {
System.out.println("物件建立失敗!");
e.printStackTrace();
}
//關閉資源,先關閉PreparedStatement物件,然後關閉連線物件
if (pstmt!=null){
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
最後將PageProcessor和Pipeline程式碼貼上
PageProcessor:
package music163;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Json;
/**
* 爬取網頁版網易雲中歌單的基本資訊,並且將資料存入資料庫中
*/
public class music163 implements PageProcessor {
private Site site = Site.me().setSleepTime(1000).setRetryTimes(3).addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0");
//建立一個靜態成員用於拼接url。
private static String basic = "https://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset=";
@Override
public Site getSite() {
return site;
}
@Override
public void process(Page page) {
//將資料交給pipeline檔案處理
page.putField("name",page.getHtml().xpath("//li/p[@class=dec]/a/text()").all());
page.putField("src",page.getHtml().xpath("//li/p[@class=dec]/a/@href").all());
page.putField("clicknum",page.getHtml().xpath("//li//div[@class=bottom]/span[@class=nb]/text()").all());
page.putField("author",page.getHtml().xpath("//li//a[@class=nm]/text()").all());
page.putField("homepage",page.getHtml().xpath("//li//a[@class=nm]/@href").all());
//連續爬取後續網頁
int flag = 35;
String nexturl = null;
for (int i=flag; i<1436;){
nexturl = basic+i;
page.addTargetRequest(nexturl);
i+=35;
}
}
public static void main(String[] args){
Spider.create(new music163())
.addUrl(basic+"0")
.addPipeline(new music163Pipeline())
.thread(3)
.run();
}
}
Pipeline:
package music163;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
public class music163Pipeline implements Pipeline {
//處理item資料
@Override
public void process(ResultItems items, Task t) {
List<String> list_name = new ArrayList<String>(); //歌單名稱
List<String> list_src = new ArrayList<String>(); //歌單地址
List<String> list_clicknum = new ArrayList<String>(); //歌單播放量
List<String> list_author = new ArrayList<String>(); //歌單作者
List<String> list_author_homepage = new ArrayList<String>();//作者主頁
//建立歌單地址和作者主頁地址的域 url
String basicsrc = "https://music.163.com";
//提取所有需要的資訊
list_name.addAll(items.get("name"));
list_src.addAll(items.get("src"));
list_clicknum.addAll(items.get("clicknum"));
list_author.addAll(items.get("author"));
list_author_homepage.addAll(items.get("homepage"));
int len=list_src.size();
for(int i=0; i<len; i++){
//修改歌單地址
list_src.set(i, basicsrc+list_src.get(i));
//修改作者個人主頁地址
list_author_homepage.set(i, basicsrc+list_author_homepage.get(i));
}
//建立連線物件
Connection conn = null;
//載入資料庫驅動
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("載入驅動失敗!");
e.printStackTrace();
}
//連線資料庫
String url = "jdbc:mysql://localhost:3306/test"; //這是資料庫的位置
//進行連線
try {
conn = DriverManager.getConnection(url, "root", "lanqiyu0328");
//因為需要批量提交資料,所以設定conn的提交方式為手動
conn.setAutoCommit(false);
} catch (SQLException e) {
System.out.println("連線失敗!");
e.printStackTrace();
}
//建立PrepareStatement物件,用來執行sql語句
String sql = "insert ignore into music_list value(?,?,?,?,?)";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql); //建立PreparedStatement物件
//將資料一次放入sql語句裡面
int num = list_name.size();
for(int i=0; i<num; i++){
pstmt.setString(1, list_name.get(i));
pstmt.setString(2, list_src.get(i));
pstmt.setString(3, list_clicknum.get(i));
pstmt.setString(4, list_author.get(i));
pstmt.setString(5, list_author_homepage.get(i));
//將資料加入批量處理
pstmt.addBatch();
}
//批量處理資料之後手動提交
pstmt.executeBatch();
conn.commit();
} catch (SQLException e) {
System.out.println("物件建立失敗!");
e.printStackTrace();
}
//關閉資源,先關閉PreparedStatement物件,然後關閉連線物件
if (pstmt!=null){
try {
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
如果大家發現有錯誤,希望可以提出來。謝謝。