首页 > 代码库 > 爬虫代码实现五:解析所有分页url并优化解析实现类

爬虫代码实现五:解析所有分页url并优化解析实现类

 

 

技术分享

如图,我们进入优酷首页,可以看到电视剧列表,我们称这个页面为电视剧列表页,而点击进入某个电视剧,则称为电视剧详情页。那么如何获取所有分页以及对应的详情页呢,通过下面的分页得到。

因此,首先,我们将StartDSJCount中的url从详情页改为列表页,

技术分享

 

 由于这里我们想获取列表页对应的所有分页详情页,因此,我们需要在page中添加一个urlList属性,然后给它get/set方法。这里如果自动生成set方法,那么我们在set时还要new一个list,有点麻烦,这里我们先暂时只自动生成get方法,然后我们自己手动去写set方法。

而在这个set方法中,我们传入的参数是一个url而不是一个urlList,这样我们只要传入这一个url,然后在set方法中将这个url添加到list中去,而如果传一个urlList作为参数,则每次都要new一个list。

Page类重构如下:

package com.dajiangtai.djt_spider.entity;

import java.util.ArrayList;
import java.util.List;

/**
* 存储页面信息实体类
* @author dajiangtai
* created by 2017-01-09
*
*/
public class Page {

//页面内容
private String content;

//总播放量
private String allnumber;

//每日播放增量
private String daynumber;

//评论数
private String commentnumber;

//收藏数
private String collectnumber;

//赞
private String supportnumber;

//踩
private String againstnumber;

//电视剧名称
private String tvname;

//页面url
private String url;

//子集数据
private String episodenumber;

//电视剧id
private String tvId;

//存储电视剧url(包括列表页url和详情页url)
private List<String> urlList = new ArrayList<String>();

//自动生成get方法
public List<String> getUrlList() {
return urlList;
}

//手动写set方法,即addUrl方法,传入一个url,同样实现setUrlList效果

public void addUrl(String url){
this.urlList.add(url);
}
public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getAllnumber() {
return allnumber;
}

public void setAllnumber(String allnumber) {
this.allnumber = allnumber;
}

public String getDaynumber() {
return daynumber;
}

public void setDaynumber(String daynumber) {
this.daynumber = daynumber;
}

public String getCommentnumber() {
return commentnumber;
}

public void setCommentnumber(String commentnumber) {
this.commentnumber = commentnumber;
}

public String getCollectnumber() {
return collectnumber;
}

public void setCollectnumber(String collectnumber) {
this.collectnumber = collectnumber;
}

public String getSupportnumber() {
return supportnumber;
}

public void setSupportnumber(String supportnumber) {
this.supportnumber = supportnumber;
}

public String getAgainstnumber() {
return againstnumber;
}

public void setAgainstnumber(String againstnumber) {
this.againstnumber = againstnumber;
}

public String getTvname() {
return tvname;
}

public void setTvname(String tvname) {
this.tvname = tvname;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getEpisodenumber() {
return episodenumber;
}

public void setEpisodenumber(String episodenumber) {
this.episodenumber = episodenumber;
}

public String getTvId() {
return tvId;
}

public void setTvId(String tvId) {
this.tvId = tvId;
}


}

 接下来。我们在YOUKUProcessService重构,对url分别进行判断,如果是详情页,则...处理。也就是,将原来的详情页处理的代码都放到if判断处理括号中去。

技术分享

同时,YOUKUProcessService中原来详情页的处理代码,全部封装到一个方法中去,通过观察,我们只需要传入TagNode rootNode,Page page即可。其余的代码原封不动的复制到这个方法中。

因此,YOUKUProcessService代码重构如下:

package com.dajiangtai.djt_spider.service.impl;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

import com.dajiangtai.djt_spider.entity.Page;
import com.dajiangtai.djt_spider.service.IProcessService;
import com.dajiangtai.djt_spider.util.HtmlUtil;
import com.dajiangtai.djt_spider.util.LoadPropertyUtil;
import com.dajiangtai.djt_spider.util.RegexUtil;

public class YOUKUProcessService implements IProcessService {

public void process(Page page) {
String content = page.getContent();
HtmlCleaner htmlCleaner = new HtmlCleaner();
//利用htmlCleaner对网页进行解析,得到根节点
TagNode rootNode = htmlCleaner.clean(content);

//如果是详情页
if(page.getUrl().startsWith("http://www.youku.com/show_page")){
parseDetail(rootNode,page);
}else{

}

}



//解析电视剧详情页
public void parseDetail(TagNode rootNode,Page page){
// /html/body/div[4]/div/div[1]/div[2]/div[2]/ul/li[11]
//对XPath做相应的调整,使其有效,如果不该写,则使用debug模式,会发现evaluateXPath为[]
//总播放数
// String allnumber = HtmlUtil.getFieldByRegex(rootNode, parseAllNumber, allnumberRegex);
String allnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseAllNumber"), LoadPropertyUtil.getYOUKY("allnumberRegex"));
//System.out.println("总播放数数量为:"+allnumber);
page.setAllnumber(allnumber);

//总播放数
String commentnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseCommentNumber"), LoadPropertyUtil.getYOUKY("commentnumberRegex"));
//System.out.println("总评论数量为:"+commentnumber);
page.setCommentnumber(commentnumber);

//总播放数
String supportnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseSupportNumber"), LoadPropertyUtil.getYOUKY("supportnumberRegex"));
//System.out.println("总评论数量为:"+supportnumber);
page.setSupportnumber(supportnumber);

page.setDaynumber("0");
page.setAgainstnumber("0");
page.setCollectnumber("0");

//Pattern numberPattern = Pattern.compile(regex,Pattern.DOTALL);
Pattern numberPattern = Pattern.compile(LoadPropertyUtil.getYOUKY("idRegex"),Pattern.DOTALL);
page.setTvId("tvId_"+RegexUtil.getPageInfoByRegex(page.getUrl(), numberPattern, 1));
}
}

以上是详情页处理,那么如果是列表页怎么处理呢?

我们打开一个列表页,然后保存这个列表页url的下一页的url。如何获取到当前这一页的下一页的url呢?通过

技术分享

点击这个红色部分实现。接下来我们按F12查看代码是怎么回事。

1.我们获取到红色部分的XPath:

技术分享

这段XPath对应的代码如下:

技术分享

我们通过这个XPath直接获取到它的下一个链接地址href即可。

2.我们将这个XPath添加到配置文件中去。nextUrlRegex=//*[@id="m13050845531"]/div[2]/div/div/div[2]/a

allnumberRegex=(?<=\u603B\u64AD\u653E\u6570\uFF1A)[\\d,]+
commentnumberRegex=(?<=\u8BC4\u8BBA\uFF1A)[\\d,]+
supportnumberRegex=(?<=\u9876\uFF1A)[\\d,]+
parseAllNumber=/body/div/div/div/div/div/ul/li[11]
parseCommentNumber=//div[@class=\"p-base\"]/ul/li[12]
parseSupportNumber=//div[@class=\"p-base\"]/ul/li[13]
idRegex=http://list.youku.com/show/id_([\\w]+).html
nextUrlRegex=//*[@id="m13050845531"]/div[2]/div/div/div[2]/a

 

3.其实这里的思想很简单,通过xpath定位到a标签。那么,else如何写代码呢?在这里,我们封装好一个方法,用于或许xpath对应代码中的属性值。

因此,在HtmlUtil中添加一个方法,用于获取标签属性值

package com.dajiangtai.djt_spider.util;

import java.util.regex.Pattern;

import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

/**
* 页面解析工具类
* @author Administrator
*
*/
public class HtmlUtil {

//获取标签属性值
public static String getAttributeByName(TagNode rootNode,String xpath,String att){
String result = null;
Object[] evaluateXPath = null;
try {
evaluateXPath = rootNode.evaluateXPath(xpath);
if(evaluateXPath.length>0){
TagNode node = (TagNode)evaluateXPath[0];
return node.getAttributeByName(att);
}
} catch (XPatherException e) {
e.printStackTrace();
}
return result;
}

public static String getFieldByRegex(TagNode rootNode,String xpath,String regex){

String number = "0";
Object[] evaluateXPath = null;
try {
evaluateXPath = rootNode.evaluateXPath(xpath);
if(evaluateXPath.length>0){
TagNode node = (TagNode)evaluateXPath[0];
Pattern numberPattern = Pattern.compile(regex,Pattern.DOTALL);
number = RegexUtil.getPageInfoByRegex(node.getText().toString(), numberPattern, 0);
}
} catch (XPatherException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return number;



}
}

4.如何获取下一页呢?直接调用HtmlUtil封装好的方法:

String nextUrl = HtmlUtil.getAttributeByName(rootNode, LoadPropertyUtil.getYOUKY("nextUrlRegex"), "href");

5.

技术分享

 

那如果是点击到了34页,再查看下一页对应的代码,则为红色部分,没有href属性,那么nextUrl也就为空。

如果它不为空,我们就将这个下一页的url添加到urlList集合中。所以,代码如下:

String nextUrl = HtmlUtil.getAttributeByName(rootNode, LoadPropertyUtil.getYOUKY("nextUrlRegex"), "href");
if(nextUrl!=null){
page.addUrl(nextUrl);
}

6.以上是获取下一页的url,现在我们需要获取分页显示的所有详情页的url。我们随便点击两个电视剧查看它的xpath,发现:

技术分享

它俩的共性在,不同的ul下的li[1]/a,因此,我们只需要改成公用的ul即可。在youku.properties添加:eachDetailUrlRegex=//*[@id="m13050845531"]/div[1]/div/ul/li[1]/a。

它表示所有的ul下的li[1]/a,也就是当前页显示的所有电视剧的xpath。

7.通过eachDetailUrlRegex我们可以获取到多个ul下的li[1]/a的xpath,然后调用rootNode.evaluateXPath(LoadPropertyUtil.getYOUKY("eachDetailUrlRegex"));得到一个数组,再对数组进行遍历,也就是找到所有的详情页的a,然后读取href属性,再将详情页的url也加到urlList集合中。
if(evaluateXPath.length>0){

//获取下一页的url
String nextUrl = HtmlUtil.getAttributeByName(rootNode, LoadPropertyUtil.getYOUKY("nextUrlRegex"), "href");
if(nextUrl!=null){
page.addUrl(nextUrl);  //将下一页的url添加到urlList集合中
}

//获取详情页的url
try {
Object[] evaluateXPath =
rootNode.evaluateXPath(LoadPropertyUtil.getYOUKY("eachDetailUrlRegex"));
if(evaluateXPath.length>0){
for(Object object:evaluateXPath){
TagNode tagnode = (TagNode) object;
String detailUrl = tagnode.getAttributeByName("href");
page.addUrl(detailUrl);//将详情页的url添加到urlList集合中
}
}

8.最后,YOUKUProcessService 代码如下:

package com.dajiangtai.djt_spider.service.impl;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

import com.dajiangtai.djt_spider.entity.Page;
import com.dajiangtai.djt_spider.service.IProcessService;
import com.dajiangtai.djt_spider.util.HtmlUtil;
import com.dajiangtai.djt_spider.util.LoadPropertyUtil;
import com.dajiangtai.djt_spider.util.RegexUtil;

public class YOUKUProcessService implements IProcessService {

public void process(Page page) {
String content = page.getContent();
HtmlCleaner htmlCleaner = new HtmlCleaner();
//利用htmlCleaner对网页进行解析,得到根节点
TagNode rootNode = htmlCleaner.clean(content);

//如果是详情页
if(page.getUrl().startsWith("http://www.youku.com/show_page")){
parseDetail(rootNode,page);
}else{
//获取下一页的url
String nextUrl = HtmlUtil.getAttributeByName(rootNode, LoadPropertyUtil.getYOUKY("nextUrlRegex"), "href");
if(nextUrl!=null){
System.out.println(nextUrl); //打印下一页的url
page.addUrl(nextUrl);
}

//获取详情页的url
try {
Object[] evaluateXPath =
rootNode.evaluateXPath(LoadPropertyUtil.getYOUKY("eachDetailUrlRegex"));
if(evaluateXPath.length>0){
for(Object object:evaluateXPath){
TagNode tagnode = (TagNode) object;
String detailUrl = tagnode.getAttributeByName("href");
System.out.println(detailUrl);//打印详情页的url
page.addUrl(detailUrl);
}
}
} catch (XPatherException e) {

e.printStackTrace();
}
}

}

//解析电视剧详情页
public void parseDetail(TagNode rootNode,Page page){
// /html/body/div[4]/div/div[1]/div[2]/div[2]/ul/li[11]
//对XPath做相应的调整,使其有效,如果不该写,则使用debug模式,会发现evaluateXPath为[]
//总播放数
// String allnumber = HtmlUtil.getFieldByRegex(rootNode, parseAllNumber, allnumberRegex);
String allnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseAllNumber"), LoadPropertyUtil.getYOUKY("allnumberRegex"));
//System.out.println("总播放数数量为:"+allnumber);
page.setAllnumber(allnumber);

//总播放数
String commentnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseCommentNumber"), LoadPropertyUtil.getYOUKY("commentnumberRegex"));
//System.out.println("总评论数量为:"+commentnumber);
page.setCommentnumber(commentnumber);

//总播放数
String supportnumber = HtmlUtil.getFieldByRegex(rootNode, LoadPropertyUtil.getYOUKY("parseSupportNumber"), LoadPropertyUtil.getYOUKY("supportnumberRegex"));
//System.out.println("总评论数量为:"+supportnumber);
page.setSupportnumber(supportnumber);

page.setDaynumber("0");
page.setAgainstnumber("0");
page.setCollectnumber("0");

//Pattern numberPattern = Pattern.compile(regex,Pattern.DOTALL);
Pattern numberPattern = Pattern.compile(LoadPropertyUtil.getYOUKY("idRegex"),Pattern.DOTALL);
page.setTvId("tvId_"+RegexUtil.getPageInfoByRegex(page.getUrl(), numberPattern, 1));
}
}

9.这里运行StartDSJCount的main方法,输出的值都为null,采用debug模式调试,只要一运行,就报异常

技术分享

 

 原因:我们测试的url不是之前的详情页的url,而是电视列表的url,如果是浏览器输入该url没有问题,但是如果是通过程序去访问,则有些网站往往采取反爬虫策略,当它测试到是程序在读取url访问,就会自动阻止。这个问题怎么解决呢?我们通过浏览器模拟访问,而不是单纯的通过程序来访问。

技术分享

按F12,选择network,再刷新网页,再选择network栏最上方显示的,发现Headers下有个User代码

技术分享

重构PageDownLoadUtil代码:

1.定义private final static String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";;

2.request.setHeader("User-Agent",USER_AGENT);

PageDownLoadUtil代码如下:

package com.dajiangtai.djt_spider.util;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.dajiangtai.djt_spider.entity.Page;
import com.dajiangtai.djt_spider.service.impl.HttpClientDownLoadService;


/**
* 页面下载工具:将整个页面的html源码全部下载下来
* @author lch
* created by 2017/1/9
*
*/
public class PageDownLoadUtil {

private final static String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";

public static String getPageContent(String url){
HttpClientBuilder builder = HttpClients.custom();
CloseableHttpClient client = builder.build();

HttpGet request = new HttpGet(url);
String content = null;
try {
request.setHeader("User-Agent",USER_AGENT);
CloseableHttpResponse response = client.execute(request);
HttpEntity entity = response.getEntity();
content = EntityUtils.toString(entity,"utf-8");
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return content;

}

public static void main(String[] args) {
String url = "http://list.youku.com/show/id_z9cd2277647d311e5b692.html?spm=a2h0j.8191423.sMain.5~5~A!2.iCUyO9";
//String content = PageDownLoadUtil.getPageContent(url);
//System.out.println(content);
HttpClientDownLoadService down = new HttpClientDownLoadService();
Page page = down.download(url);
System.out.println(page.getContent());
}
}

 

再次运行StartDSJCount的main方法测试,如下:

技术分享

下一页的url以及详情页的url是输出了,但是,爬取的这些值都为null。为什么呢?

其实这里为null是正常的。因为,我们测试的url是列表url,而不是详情页url,只有详情页才有总播放量、评论数、赞等。

 

爬虫代码实现五:解析所有分页url并优化解析实现类