文章目录
前言
最近在学习过程中接到一个任务,要求爬取
阿里拍卖
中法院拍卖的所有拍卖品。用了点时间完成了任务,并分享出来作为经验供学习、交流。若文中有任何不妥之处请提出。
最终效果
效果演示
爬取所有记录
根据条件爬取
项目仓库
项目前准备
技术选型
- HTML解析技术:Jsoup
jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
想了解Jsoup相关内容,请移步:https://jsoup.org/
开发工具
- IDEA 2018.2
- MYSQL 5.7
- Chrome
用到的jar包
- easy-poi:3.0.3
- mysql-connector-java:5.1.40
- jsoup:1.8.3
- fastjson:1.2.47
爬取所有数据
页面分析
- 首先我们通过
http://sf.taobao.com/court_list.htm?spm=a213w.3065169.sfhead2014.3.3e1f1a333zcUBm
,进入要爬取的入口页,入口页的结构如下图所示:
页面的机构是以省份为区分,在省下包含市,市包含法院,而法院中的链接市我们要获取信息的下一个入口。为了实现目的,爬虫的处理逻辑如下:
1.进入入口页,获取所有省份的DOM
2.对第一个省份继续进行处理,获取市级列表
3.对第一个市进行处理,获取法院列表和链接地址
4.进入第一个法院,获得拍卖品列表
5.进入商品详情页获取需要的信息
6.重复5,知道所有拍卖品爬完
7.重复4-6,直至所有法院爬完
8.重复3-7,直至所有市爬完
9.重复2-8,直至所有省份爬完
10.结束
第一步:获取所有省份列表
- 首先用Chrome浏览器检查爬虫入口页面元素,这样能够清晰直观看出页面的布局,如下图所示
图中,1是省份的div
,包裹在一个class="provinces clearfix"
的div
中,在1中还包含了2,3,4这些元素,这些都是我们接下来需要的内容。
图中,2是省份的名称,包裹在一个class="province"
的div
中,通过查看多个省份的内容可以发现,这个模型中只会有省份名称这个中文,所以取出这个信息时可以通过正则表达式对中文进行匹配,从而取出值。
图中,3是省份下的市列表,包裹在一个class="province"
的div
中。每个市在一个单独的class="city"
的div
中。图中的4,市该市下的法院列表,法院列表中每个法院的链接才是我们需要的内容,通过访问链接才能进入该法院的拍卖品列表,每个法院的链接规则如下:
获取也很简答,只用获取a
标签中的href
属性即可,但访问时为了比较明显,要在前面加个http:
点击链接,进入拍卖品列表,如下:
都这样的结构也很简单,第一想法时使用上述相同的方法直接一条一条爬取就行。但如果这样你会发现,并爬不到数据,后来才发现,所有的数据并不是在页面请求时就直接加载的,所有的数据会以json的格式放在一个script
标签中,如下图所示:
这样就更简单了,直接通过Json解析,然后取出详情链接就可以了。但需要注意的是,数据只是这一页中的,事实上数据可能有很多页。所以这时的想法就是看下每页的请求路径有什么规律,通过对比就发现翻页时的路径类似于http://sf.taobao.com/court_item.htm?spm=a213w.7398554.pagination.1.5edd2fc2Vn735Z&user_id=2364124517&auction_start_seg=-1&page=2
,而页码对应的就是page
的值,所以我们只需要从页面上获取总计页数,通过循环就可以得到每页的数据。
方法很简单,只需要获取到class="page-total"的em
标签的text
内容即可
接下来就是详情页,详情页我们只抓三个部分的内容
其中标题和变卖价的规则如下:
这里根据之前的规则就可以取出值了。接下来通过代码来实现这一过程
代码实现
本文前面已经附带了
github
的地址,原项目使用gradle
构建,需要的朋友可以参考。所以本部分以关键部分代码讲解为主。
进入主页
Document document = Jsoup.connect(ConValues.SF_URL_ENTRANCE).timeout(5000).get();
这一步比较简单,需要注意的是最好加上timeout()
方法设置超时时间,不然可能会出现java.net.SocketTimeoutException:Read timed out
异常。通过这一步就可以得到文档对象,来完成下列操作。
解析页面
获得所有省的文档模型
Elements select = document.select("div[class=provinces clearfix]");
循环,获取各省的信息
Elements elements = element.select("div[class=province"); Matcher valueByReg = DataUtil.getValueByReg(ConValues.ZHONG_WEN_REG, elements.toString());
if(valueByReg.find()){
//省
province = valueByReg.group(0);
}
获得市列表
Elements citys = var1.select("dl[class=city]");
获得总页数和翻页时的路径规则
Elements var4 = var3.select("a");
for(Element var5:var4){
if(var5.hasAttr("rel")){
basePageUrl = "http:"+var5.attr("href").trim();
break;
}
}
String pageText = var3.select("span[class=page-skip]").select("em[class=page-total]").text();
if(pageText.length()>0){
totalPage = Integer.parseInt(pageText);
}
进入拍卖品列表页并取出值
//对每一页进行处理
String pageUrl = basePageUrl.substring(0,basePageUrl.length()-1)+i;
Document document2 = Jsoup.connect(pageUrl).timeout(5000).get();
//获得该页所有商品信息,该页的所有商品信息是以json的格式存放在<script>标签中的
String oriData = document2.getElementById("sf-item-list-data").toString();
//接下来对数据进行处理
//1.找到“>”标签的位置
int start = oriData.indexOf(">");
//2.找到"</"标签的位置
int end = oriData.indexOf("</");
//截取出值
String data = oriData.substring(start+1, end);
解析json
数据获得详情路径,并提取数据
JSONArray data1 = (JSONArray) JsonUtil.convertJsonStrToMap(data).get("data");
Iterator<Object> iterator = data1.iterator();
while(iterator.hasNext()){
JSONObject next = (JSONObject)iterator.next();
//需要记录的url
String detailUrl = "http:"+next.get("itemUrl");
//获取标题
Document document3 = Jsoup.connect(detailUrl).timeout(5000).get();
String detailTitle = document3.select("div[class=pm-main clearfix]").select("h1").text();
//获取变卖价
String detailPrice = document3.select("span[class=J_Price]").first().text();
System.out.println("抓取信息:");
System.out.println(province+" "+city+" "+countryName+" "+detailTitle+" "+detailPrice+" "+detailUrl);
}
持久化数据库
定义POJO
public class AuctionItem {
private String province;//省份
private String city;//城市
private String countryName;//法院
private String detailTitle;//标题
private String detailPrice;//变卖价
private String detailUrl;//资源路径
//getter and setter
.............
}
构造数据库操作工厂类
public class DbOpFactory {
// JDBC 驱动名及数据库 URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/spider-data";
private static DbOpFactory instance;
public static DbOpFactory getInstance(){
if(instance==null){
return new DbOpFactory();
}else{
return instance;
}
}
private DbOpFactory(){
init();
}
// 数据库的用户名与密码,需要根据自己的设置
static final String USER = "root";
static final String PASS = "xda265856";
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
public void init() {
// 注册 JDBC 驱动
try {
Class.forName("com.mysql.jdbc.Driver");
// 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 持久化拍卖信息
* @param auctionItem
*/
public void insertAuction(AuctionItem auctionItem){
try{
// 执行查询
// System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = String.format("INSERT INTO TB_AUCTION_ITEM(AUCTION_PROVINCE,AUCTION_CITY,AUCTION_COUNTRY_NAME,AUCTION_DETAIL_TITLE,AUCTION_PRICE,AUCTION_DETAIL_URL) VALUES('%s','%s','%s','%s','%s','%s')"
,auctionItem.getProvince(),auctionItem.getCity(),auctionItem.getCountryName(),auctionItem.getDetailTitle(),auctionItem.getDetailPrice(),auctionItem.getDetailUrl());
stmt.executeUpdate(sql);
}catch(SQLException se){
// 处理 JDBC 错误
se.printStackTrace();
}catch(Exception e){
// 处理 Class.forName 错误
e.printStackTrace();
}
public void close(){
// 完成后关闭
try {
stmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
执行插入
DbOpFactory instance = DbOpFactory.getInstance();
instance.init();
........
AuctionItem auctionItem = new AuctionItem();
auctionItem.setProvince(province);
auctionItem.setCity(city);
auctionItem.setCountryName(countryName);
auctionItem.setDetailTitle(detailTitle);
auctionItem.setDetailPrice(detailPrice);
auctionItem.setDetailUrl(detailUrl);
instance.insertAuction(auctionItem);
System.out.println("存入数据库成功");
........
instance.close();
至此爬虫完成,上述代码有不详尽的地方请到github
上查看源码。
根据搜索条件爬取数据
根据条件的爬虫时根据搜索栏中输入的关键字进行查询,这里将需要查询的信息放到EXCEL表格中,方便获取。
分析
总体思路于爬取所有的内容时相同的。
- 在搜索框中输入关键字
- 搜索结果页面的地址如下
值为http://sf.taobao.com/list/0.htm?auction_start_seg=-1&q=%CE%E2%BD%AD%CB%C9%C1%EA%D5%F2%BD%AD%D8%C7%C2%B78%BA%C5A16-1&page=1
,令人费解的是%CE%E2%BD%AD%CB%C9%C1%EA%D5%F2%BD%AD%D8%C7%C2%B78%BA%C5A16-1
这串字符串。猜测应该是搜索的关键字但具体是什么就不得而知。通过了解发现这是URL字符转义(想了解更多,请自行百度)
,所以我们通过下面的方法可以实现对URL的字符转义功能
/**
* url字符转码
*/
public static String getURLEncode(String urlValue){
String urlEncode= null;
try {
urlEncode = java.net.URLEncoder.encode(urlValue, "gb2312");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return urlEncode;
}
我们通过对路径的拆分、拼接,即可得到完整的路径
//查询地址前面部分
public static String SEARCH_ADDRESS_PREFIX = "http://sf.taobao.com/item_list.htm?q=";
//查询地址后面部分
public static String SEARCH_ADDRESS_SUFFIX = "&spm=a213w.3064813.9001.1";
从EXCEL中获得查询关键字
这里使用Easy-poi获取excel中的内容
- 定义pojo
public class SearchAttribute {
@Excel(name = "序号")
private String id;
@Excel(name = "地址")
private String address;
@Excel(name = "所有人")
private String owner;
@Excel(name = "产证号1")
private String certNum1;
@Excel(name = "产证号2")
private String certNum2;
@Excel(name = "产证号3")
private String certNum3;
//getter and setter
...................
}
- 获取信息
public class PoiUtils {
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws Exception {
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new Exception("模板不能为空");
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e.getMessage());
}
return list;
}
}
//得到Excel中的信息
List<SearchAttribute> allAttributes = PoiUtils.importExcel("file/paimai.xlsx", 0, 1, SearchAttribute.class);
剩下的内容和爬取所有内容相同在此不做赘述
总结
- 总体上来说功能实现了,但还有很多细节需要优化
- 如果有任何问题欢迎留言探讨
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13179.html