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

author author     2022-08-21     269

关键词:

 

 

技术分享

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

因此,首先,我们将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,只有详情页才有总播放量、评论数、赞等。

 















































































































































































































































































































































2023爬虫学习笔记--优化xpath解析代码

一、目标网站https://www.aqistudy.cn/historydata/二、分析要获取数据,热门城市、全部城市三、代码实现1、获取网页源码importrequests目标地址="https://www.aqistudy.cn/historydata/"头="User-Agent":"Mozilla/5.0(WindowsNT10.0;WOW64)A... 查看详情

爬虫代码实现三:打通爬虫项目的下载解析存储流程

1.新建一个存储接口IStoreServicepackagecom.dajiangtai.djt_spider.service;importcom.dajiangtai.djt_spider.entity.Page;/***数据存储接口*@authorAdministrator**/publicinterfaceIStoreService{ publicvoidstore(Pagepage);}2 查看详情

爬虫学习12.scrapy框架之递归解析和post请求(代码片段)

爬虫学习12.scrapy框架之递归解析和post请求今日概要递归爬取解析多页页面数据scrapy核心组件工作流程scrapy的post请求发送今日详情1.递归爬取解析多页页面数据-需求:将糗事百科所有页码的作者和段子内容数据进行爬取切持久化... 查看详情

实现自动解析properties文件并装配到bean(代码片段)

...功就使用Method.invoke()方法,执行匹配成功的方法 JAVA代码1packagebingosoft.metro.xmgl.cx.redisUtil;23importjava.io.IOExcept 查看详情

php并发包5--同步工具countdownlatchcyclicbarriersemaphore的实现原理解析(代码片段)

前言:JUC中提供了很多同步工具类,比如CountDownLatch、CyclicBarrier、Semaphore等,都可以作用同步手段来实现多线程之间的同步效果一、CountDownLatch1.1、CountDownLatch的使用CountDownLatch可以理解为是同步计数器,作用是允许一个或多个线... 查看详情

python爬虫能做啥

Python是一门非常适合开发网络爬虫的编程语言,相比于其他静态编程语言,Python抓取网页文档的接口更简洁;相比于其他动态脚本语言,Python的urllib2包提供了较为完整的访问网页文档的API。此外,python中有优秀的第三方包可以高... 查看详情

xml解析数据信息并实现dbmanager操作mysql(代码片段)

...于我每次都只要修改一下我的xml配置文件不需要修改我的代码这也就有了下面的操作将驱动url用户名和密码都写到xml文件里面 1<?xmlversion="1.0"encoding="UTF-8"?>2< 查看详情

编写spider爬取

改写parse函数实现功能:1.获取文章列表页中的文章url并交给scrapy下载后,交给解析函数进行具体字段的解析2.获取下一页的url并交给scrapy进行下载,下载完成后交给parse提取一页列表中的文章url#解析列表页中所有文章的url,遍历... 查看详情

爬虫-三种数据解析方式(代码片段)

...析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。因此,本次课程中会给大家详细介绍讲解三种聚焦爬虫中的数据解析方式。至此,我们的数据爬取的... 查看详情

python爬虫优化和错误日志分析(代码片段)

发现问题在爬虫下载过程中,执行一段时间后都会异常终止,下次必须kill掉进程重新运行,看能否优化并减少手动操作错误日志分析收集了nohup.out文件,发现主要错误是的数组下标越界,推测可能的问题为:1)网络不稳定,http... 查看详情

爬虫三种解析方法(代码片段)

...析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。因此,本次课程中会给大家详细介绍讲解三种聚焦爬虫中的数据解析方式。至此,我们的数据爬取的... 查看详情

解决猫眼网反爬虫策略的爬虫(代码片段)

项目代码:Github[目录]一.引入问题二.分步实现1.页面爬取2.woff下载3.字体解析规则一.引入问题可以看到,猫眼网电影评分,票房等的数据在响应的html中并不是直接提供给你的。这里的xefcf,xef87等数据,是以‘特殊符号’的形式显示出... 查看详情

爬虫实现:根据ip地址反查域名(代码片段)

...不会开放?);另外一种方式就是接下来我要分享的——爬虫实现:根据IP地址反查域名。实现原理实现原理其实很简单,现在已有网站提供了根据IP地址查询域名的功能,但是需要人为登录网站输入IP地址查询,我想要实现程序... 查看详情

asyncio多进程+pyppeteer浏览器控制+pyquery解析实现爬虫demo(代码片段)

importasynciofrompyppeteerimportlaunchfrompyqueryimportPyQueryaspqasyncdefmain():browser=awaitlaunch()page=awaitbrowser.newPage()awaitpage.goto(‘https://dynamic2.scrape.cuiqingcai.com/‘)awaitpage.wait 查看详情

优酷电视剧爬虫代码实现一:下载解析视频网站页面补充知识点:xpath无效怎么办?

...得到的,可是放在代码里就是不对呢?前提:优酷电视剧爬虫代码实现一:下载解析视频网站页面(2)工作量已经完成。基于这个基础,进一步完善代码1.新建页面解析接口。packagecom.dajiangtai.djt_spider.ser 查看详情

arraylist源码解析

...下基础,阅读下jdk的源码,这篇文章是来介绍下ArrayList的实现。1.ArrayList概述    List 接口的大小可变数组的实现,位于API文档的java.util.ArrayList<E>。实现了所有可选列表操作,并允许包括null在内的所有元素。除了实... 查看详情

深入源码解析springaop实现的三个过程(代码片段)

...cglib动态代理的类一般是没有实现接口的类,cglib是一个代码生成的类库,可以在运行时动态生成某个类的子类,所以,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。AO... 查看详情

04.三种数据解析方式(代码片段)

...析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。因此,本次课程中会给大家详细介绍讲解三种聚焦爬虫中的数据解析方式。至此,我们的数据爬取的... 查看详情