【Java爬虫学习】WebMagic框架爬虫学习实战一:爬取网易云歌单信息,并存入mysql中
最近,需要使用Java进行爬虫编写,就去学了Java的爬虫。因为之前学习了Scrapy框架,所以学Java的爬虫使用了WebMagic框架,这个框架是基于Scrapy框架开发的。大家有兴趣可以去看看操作文档:
这个框架是国人开发的,所以说明文档都是中文,简单易懂。
导入WebMagic框架的方法在操作文档中有,在这就不讲述了(建议看这篇文章前,先去看完操作文档。我是导入jar包使用)
我使用的版本是0.7.3。
这个项目是用webmagic框架抓取网易云歌单,将爬取到的内容存入mysql中。这个demo之前用Python实现过,有兴趣的可以去看一下我之前的这篇文章 https://blog.csdn.net/chibuqikendeji/article/details/81225795
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();
}
}
}
}
如果大家发现有错误,希望可以提出来。谢谢。
emmmm,忘记贴结果图了,重新放上来,有两页,这里只放一页。这是程序完成时保存的信息,可能与现在的不符合,见谅。