技术选型:
WebCollector+jsoup,WebCollector进行爬取,jsoup进行html解析
实现步骤:
1.根据root url发起请求,
2.获取响应页面数据,
3. 解析并提取页面数据
4.下载并保存镜像
代码实现
1.相关封装结构说明
2.代码示例
links.java类,存放已访问过的URL路径和待访问的URL路径;
包com.etoak.crawl.link;
导入java.util.HashSet;
导入java.util.LinkedList;
导入java.util.Set;
/*
* 链接主要功能;
* 1: 存储已访问过的URL路径和待访问的URL路径;
* */
公开课链接{
//已访问过的url集合。主要考虑是不能重复。使用set保证不重复;
私有静态Set VisitedUrlSet=new HashSet();
//要访问的url集合。访问主要考虑的是1:规定访问顺序; 2: 确保不提供重复的访问地址;
私有静态LinkedList unVisitedUrlQueue=new LinkedList();
//获取访问过的URL数量
公共静态int getVisitedUrlNum() {
返回visitUrlSet.size();
}
//添加到访问过的URL
公共静态无效addVisitedUrlSet(字符串url){
已访问UrlSet.add(url);
}
//删除访问过的URL
公共静态无效removeVisitedUrlSet(字符串url){
VisitUrlSet.remove(url);
}
//获取要访问的url集合
公共静态LinkedList getUnVisitedUrlQueue() {
返回未访问的UrlQueue;
}
//添加到要访问的集合中,保证每个URL只被访问一次
公共静态无效addUnvisitedUrlQueue(字符串url){
if (url !=null !url.trim().equals("") !visitedUrlSet.contains(url) !unVisitedUrlQueue.contains(url)){
unVisitedUrlQueue.add(url);
}
}
//删除要访问的url
公共静态对象removeHeadOfUnVisitedUrlQueue(){
返回unVisitedUrlQueue.removeFirst();
}
//判断未访问的URL队列是否为空
公共静态布尔unVisitedUrlQueueIsEmpty(){
返回unVisitedUrlQueue.isEmpty();
}
}
Page.java类,保存获取到的响应的相关内容;
包com.etoak.crawl.page;
导入com.etoak.crawl.util.CharsetDetector;
导入org.jsoup.Jsoup;
导入org.jsoup.nodes.Document;
导入java.io.UnsupportedEncodingException;
/*
* 页
* 1: 保存获取到的响应的相关内容;
* */
公开课页面{
私有字节[]内容;
私有字符串html; //网页源代码字符串
private Document doc;//网页Dom文档
private String charset;//字符编码
private String url;//url路径
private String contentType;//内容类型
公共页面(字节[]内容,字符串url,字符串内容类型){
this.内容=内容;
这个.url=url;
this.contentType=contentType;
}
公共字符串getCharset() {
返回字符集;
}
public String getUrl(){返回url;}
公共字符串getContentType(){ 返回contentType;}
公共字节[] getContent(){ 返回内容;}
/**
* 返回网页的源代码字符串
*
* @return 网页源代码字符串
*/
公共字符串getHtml() {
如果(html!=null){
返回html;
}
如果(内容==空){
返回空值;
}
如果(字符集==空){
charset=CharsetDetector.guessEncoding(内容); //根据内容猜测字符编码
}
尝试{
this.html=new String(内容, 字符集);
返回html;
} catch (UnsupportedEncodingException ex) {
例如:printStackTrace();
返回空值;
}
}
/*
* 获取文档
* */
公共文档getDoc(){
如果(文档!=空){
返回文档;
}
尝试{
this.doc=Jsoup.parse(getHtml(), url);
返回文档;
} catch (异常前) {
例如:printStackTrace();
返回空值;
}
}
}
PageParserTool.java类,处理html页面
包com.etoak.crawl.page;
导入org.jsoup.nodes.Element;
导入org.jsoup.select.Elements;
导入java.util.ArrayList;
导入java.util.HashSet;
导入java.util.Iterator;
导入java.util.Set;
公共类PageParserTool {
/* 使用选择器选择页面*/
公共静态元素选择(页面页面,字符串cssSelector){
return page.getDoc().select(cssSelector);
}
/*
* 通过css选择器获取指定元素;
*
* */
公共静态元素选择(页面页面,字符串cssSelector,int索引){
元素eles=select(page, cssSelector);
int realIndex=索引;
如果(索引0){
realIndex=eles.size() + 索引;
}
返回eles.get(realIndex);
}
/**
* 获取元素中满足选择器的链接。选择器cssSelector 必须位于特定的超链接处。
* 比如我们要提取div中所有带有ID内容的超链接,这里
* 需要定义cssSelector为div[id=content] a
* 放入set中,防止重复;
* @param css选择器
* @返回
*/
公共静态SetgetLinks(页面页面,字符串cssSelector){
Setlinks=new HashSet();
元素es=select(page, cssSelector);
迭代器iterator=es.iterator();
while(iterator.hasNext()) {
元素element=(Element) iterator.next();
if ( element.hasAttr("href") ) {
links.add(element.attr("abs:href"));
}else if( element.hasAttr("src") ){
links.add(element.attr("abs:src"));
}
}
返回链接;
}
/**
* 获取网页中满足指定css选择器的所有元素的指定属性集合
* 例如getAttrs("img[src]","abs:src") 可用于获取网页中所有图片的链接
* @param css选择器
* @参数属性名称
* @返回
*/
公共静态ArrayListgetAttrs(页面页面,字符串cssSelector,字符串attrName){
ArrayList结果=new ArrayList();
元素eles=select(page,cssSelector);
for (元素ele : eles) {
if (ele.hasAttr(attrName)) {
结果.add(ele.attr(attrName));
}
}
返回结果;
}
}
用于请求响应的RequestAndResponseTool.java类
包com.etoak.crawl.page;
导入org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
导入org.apache.commons.httpclient.HttpClient;
导入org.apache.commons.httpclient.HttpException;
导入org.apache.commons.httpclient.HttpStatus;
导入org.apache.commons.httpclient.methods.GetMethod;
导入org.apache.commons.httpclient.params.HttpMethodParams;
导入java.io.IOException;
公共类请求和响应工具{
公共静态页面sendRequestAndGetResponse(String url) {
页页=空;
//1.生成HttpClinet对象并设置参数
HttpClient httpClient=new HttpClient();
//设置HTTP连接超时5s
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
//2.生成GetMethod对象并设置参数
GetMethod getMethod=new GetMethod(url);
//设置get请求超时5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
//设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
//3. 执行HTTP GET请求
尝试{
int statusCode=httpClient.executeMethod(getMethod);
//判断访问状态码
if (statusCode !=HttpStatus.SC_OK) {
System.err.println("方法失败:" + getMethod.getStatusLine());
}
//4.处理HTTP响应内容
byte[] responseBody=getMethod.getResponseBody(); //读取为字节数组
String contentType=getMethod.getResponseHeader("Content-Type").getValue(); //获取当前返回类型
页面=新页面(responseBody,url,contentType); //封装成页面
} catch (HttpException e) {
//发生致命异常。可能是协议不正确或者返回内容有问题。
System.out.println("请检查您提供的http地址!");
e.printStackTrace();
} catch (IOException e) {
//发生网络异常
e.printStackTrace();
} 最后{
//释放连接
getMethod.releaseConnection();
}
返回页面;
}
}
CharsetDetector.java 类,用于字符集处理
/*
* 版权所有(C)2014hu
* 本程序是免费软件;您可以重新分发它和/或
* 根据GNU通用公共许可证的条款进行修改
* 由自由软件基金会发布;任一版本2
* 许可证,或(由您选择)任何更高版本。
* 该程序发布是为了希望它有用,
* 但不提供任何保证;甚至没有默示保证
* 适销性或特定用途的适用性。请参阅
* GNU 通用公共许可证了解更多详细信息。
* 您应该已收到GNU 通用公共许可证的副本
* 与此程序一起使用;如果没有,请写信给自由软件
* Foundation, Inc.59 Temple Place - Suite 330,波士顿,MA 02111-1307,美国。
*/
包com.etoak.crawl.util;
导入org.mozilla.universalchardet.UniversalDetector;
导入java.io.UnsupportedEncodingException;
导入java.util.regex.Matcher;
导入java.util.regex.Pattern;
/**
* 字符集自动检测
* @作者胡
*/
公共类CharsetDetector {
//网页编码检测代码借用Nutch
私有静态最终int CHUNK_SIZE=2000;
私有静态模式metaPattern=Pattern.compile(
"]*http-equiv=("|")?内容类型("|")?[^]*)",
模式.CASE_INSENSITIVE);
私有静态模式charsetPattern=Pattern.compile(
"charset=\s*([a-z][_\-0-9a-z]*)", Pattern.CASE_INSENSITIVE);
私有静态模式charsetPatternHTML5=Pattern.compile(
"]*",
模式.CASE_INSENSITIVE);
//网页编码检测代码借用Nutch
私有静态字符串guessEncodingByNutch(byte[] content) {
int 长度=Math.min(content.length, CHUNK_SIZE);
字符串str="";
尝试{
str=new String(内容, "ascii");
} catch (UnsupportedEncodingException e) {
返回空值;
}
匹配器metaMatcher=metaPattern.matcher(str);
字符串编码=null;
if (metaMatcher.find()) {
匹配器charsetMatcher=charsetPattern.matcher(metaMatcher.group(1));
if (charsetMatcher.find()) {
编码=new String(charsetMatcher.group(1));
}
}
如果(编码==空){
metaMatcher=charsetPatternHTML5.matcher(str);
如果(metaMatcher.find()) {
编码=
new String(metaMatcher.group(1)); } } if (encoding == null) { if (length >= 3 && content[0] == (byte) 0xEF && content[1] == (byte) 0xBB && content[2] == (byte) 0xBF) { encoding = "UTF-8"; } else if (length >= 2) { if (content[0] == (byte) 0xFF && content[1] == (byte) 0xFE) { encoding = "UTF-16LE"; } else if (content[0] == (byte) 0xFE && content[1] == (byte) 0xFF) { encoding = "UTF-16BE"; } } } return encoding; } /** * 根据字节数组,猜测可能的字符集,如果检测失败,返回utf-8 * * @param bytes 待检测的字节数组 * @return 可能的字符集,如果检测失败,返回utf-8 */ public static String guessEncodingByMozilla(byte[] bytes) { String DEFAULT_ENCODING = "UTF-8"; UniversalDetector detector = new UniversalDetector(null); detector.handleData(bytes, 0, bytes.length); detector.dataEnd(); String encoding = detector.getDetectedCharset(); detector.reset(); if (encoding == null) { encoding = DEFAULT_ENCODING; } return encoding; } /** * 根据字节数组,猜测可能的字符集,如果检测失败,返回utf-8 * @param content 待检测的字节数组 * @return 可能的字符集,如果检测失败,返回utf-8 */ public static String guessEncoding(byte[] content) { String encoding; try { encoding = guessEncodingByNutch(content); } catch (Exception ex) { return guessEncodingByMozilla(content); } if (encoding == null) { encoding = guessEncodingByMozilla(content); return encoding; } else { return encoding; } } } FileTool .java本类主要是 下载那些已经访问过的文件 package com.etoak.crawl.util; import com.etoak.crawl.page.Page; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /* 本类主要是 下载那些已经访问过的文件*/ public class FileTool { private static String dirPath; /** * getMethod.getResponseHeader("Content-Type").getValue() * 根据 URL 和网页类型生成需要保存的网页的文件名,去除 URL 中的非文件名字符 */ private static String getFileNameByUrl(String url, String contentType) { //去除 http:// url = url.substring(7); //text/html 类型 if (contentType.indexOf("html") != -1) { url = url.replaceAll("[\?/:*|<>"]", "_") + ".html"; return url; } //如 application/pdf 类型 else { return url.replaceAll("[\?/:*|<>"]", "_") + "." + contentType.substring(contentType.lastIndexOf("/") + 1); } } /* * 生成目录 * */ private static void mkdir() { if (dirPath == null) { dirPath = Class.class.getClass().getResource("/").getPath() + "temp\"; } File fileDir = new File(dirPath); if (!fileDir.exists()) { fileDir.mkdir(); } } /** * 保存网页字节数组到本地文件,filePath 为要保存的文件的相对地址 */ public static void saveToLocal(Page page) { mkdir(); String fileName = getFileNameByUrl(page.getUrl(), page.getContentType()) ; String filePath = dirPath + fileName ; byte[] data = page.getContent(); try { //Files.lines(Paths.get("D:\jd.txt"), StandardCharsets.UTF_8).forEach(System.out::println); DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(filePath))); for (int i = 0; i< data.length; i++) { out.write(data[i]); } out.flush(); out.close(); System.out.println("文件:"+ fileName + "已经被存储在"+ filePath ); } catch (IOException e) { e.printStackTrace(); } } } RegexRule.java类,正则 /* * Copyright (C) 2014 hu * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.etoak.crawl.util; import java.util.ArrayList; import java.util.regex.Pattern; /** * @author hu */ public class RegexRule { public RegexRule(){ } public RegexRule(String rule){ addRule(rule); } public RegexRule(ArrayListrules){ for (String rule : rules) { addRule(rule); } } public boolean isEmpty(){ return positive.isEmpty(); } private ArrayListpositive = new ArrayList(); private ArrayListnegative = new ArrayList(); /** * 添加一个正则规则 正则规则有两种,正正则和反正则 * URL符合正则规则需要满足下面条件: 1.至少能匹配一条正正则 2.不能和任何反正则匹配 * 正正则示例:+a.*c是一条正正则,正则的内容为a.*c,起始加号表示正正则 * 反正则示例:-a.*c时一条反正则,正则的内容为a.*c,起始减号表示反正则 * 如果一个规则的起始字符不为加号且不为减号,则该正则为正正则,正则的内容为自身 * 例如a.*c是一条正正则,正则的内容为a.*c * @param rule 正则规则 * @return 自身 */ public RegexRule addRule(String rule) { if (rule.length() == 0) { return this; } char pn = rule.charAt(0); String realrule = rule.substring(1); if (pn == "+") { addPositive(realrule); } else if (pn == "-") { addNegative(realrule); } else { addPositive(rule); } return this; } /** * 添加一个正正则规则 * @param positiveregex * @return 自身 */ public RegexRule addPositive(String positiveregex) { positive.add(positiveregex); return this; } /** * 添加一个反正则规则 * @param negativeregex * @return 自身 */ public RegexRule addNegative(String negativeregex) { negative.add(negativeregex); return this; } /** * 判断输入字符串是否符合正则规则 * @param str 输入的字符串 * @return 输入字符串是否符合正则规则 */ public boolean satisfy(String str) { int state = 0; for (String nregex : negative) { if (Pattern.matches(nregex, str)) { return false; } } int count = 0; for (String pregex : positive) { if (Pattern.matches(pregex, str)) { count++; } } if (count == 0) { return false; } else { return true; } } } MyCrawler.java 爬取主类 package com.etoak.crawl.main; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import org.jsoup.select.Elements; import com.alibaba.fastjson.JSON; import com.etoak.crawl.link.LinkFilter; import com.etoak.crawl.link.Links; import com.etoak.crawl.page.Page; import com.etoak.crawl.page.PageParserTool; import com.etoak.crawl.page.RequestAndResponseTool; import com.etoak.crawl.util.FileTool; public class MyCrawler { /** * 使用种子初始化 URL 队列 * * @param seeds 种子 URL * @return */ private void initCrawlerWithSeeds(String[] seeds) { for (int i = 0; i< seeds.length; i++){ Links.addUnvisitedUrlQueue(seeds[i]); } } /** * 抓取过程 * * @param seeds * @return */ public void crawling(String[] seeds , String name) { //初始化 URL 队列 initCrawlerWithSeeds(seeds); //定义过滤器,提取以 http://www.baidu.com 开头的链接 LinkFilter filter = new LinkFilter() { public boolean accept(String url) { if (url.startsWith("https://www.jd.com")) return true; else return false; } }; //循环条件:待抓取的链接不空且抓取的网页不多于 1000 int m = 1; for (int i = 0; i< m; i++) { //先从待访问的序列中取出第一个; String visitUrl = (String) Links.removeHeadOfUnVisitedUrlQueue(); if (visitUrl == null){ continue; } //根据URL得到page; Page page = RequestAndResponseTool.sendRequstAndGetResponse(visitUrl); //对page进行处理: 访问DOM的某个标签 System.out.println(page); Elements es = PageParserTool.select(page,"img[src]"); if(!es.isEmpty()){ System.out.println("下面将打印所有img[src]标签: "); System.out.println(es); } //得到超链接 ArrayList links = PageParserTool.getAttrs(page,"img[src]","abs:src"); m = links.size(); try { FileTool.saveToLocal(page,URLDecoder.decode(name, "utf-8")); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(links!=null&&links.size()>0){ Links.addUnvisitedUrlQueue(links.get(3)); System.out.println("新增爬取路径: " + links); } } } //main 方法入口 public static void main(String[] args) { String as ="["帆布鞋"]"; Listlist =JSON.parseArray(as); /*JSONObject json = null; json = RedisUtils.getObject("JDClassCid3"); if(json!=null){ list = (List) json.get("list"); }else{ list = ShoppingGuideUtils.getClasss11(); Mapmap = new HashMap(); map.put("list", list); RedisUtils.setObjectMap("JDClassCid3", map, RedisUtils.EXRP_DAY); }*/ System.out.println(list); if(list!=null&&list.size()>0){ String name= ""; for (int i = 0; i< list.size(); i++) { MyCrawler crawler = new MyCrawler(); name = list.get(i).toString(); try { if(URLDecoder.decode(name, "utf-8").contains("二手")){//去除二手物品 continue; } } catch (UnsupportedEncodingException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } try { name =URLEncoder.encode(name, "utf-8"); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } crawler.crawling(new String[]{"https://so.m.jd.com/ware/search.action?keyword="+name+"&searchFrom=category&sf=1&as=1"},name); try { System.out.println("总计"+list.size()+"个===当前分类"+URLDecoder.decode(name, "utf-8")+"爬到"+i+"个"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /*MyCrawler crawler = new MyCrawler(); String name= "ETC"; try { if(URLDecoder.decode(name, "utf-8").contains("二手")){ System.out.println("jies"); } } catch (UnsupportedEncodingException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } try { name =URLEncoder.encode(name, "utf-8"); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } crawler.crawling(new String[]{"https://so.m.jd.com/ware/search.action?keyword="+name+"&searchFrom=category&sf=1&as=1"},name); */ } } 我选的根url是京东是搜索接口,取出来合适的图片,这里难点就在这,取出来html中的img集合,匹配度最高的,这得批跑抓取好几次,还有比较有争议的分类名称,比如“苹果”这种就得特殊处理关于高效抓取商品分类图标:Java爬虫WebCollector与jsoup结合应用和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
【高效抓取商品分类图标:Java爬虫WebCollector与jsoup结合应用】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
这听起来很有用!我现在要用Java开发网站数据分析项目,这个方法可以帮上很大的忙。
有12位网友表示赞同!
我之前看过Jsoup的使用,很强大!搭配WebCollector应该能更方便地爬取分类信息吧。
有19位网友表示赞同!
学习一下网页抓取的知识听起来很有趣哦~想在自己的项目里尝试一下这个方法。
有6位网友表示赞同!
我想把商品分类图标收集下来做个数据可视化分析,这个方法挺合适的!
有14位网友表示赞同!
Java语言很棒,特别是WebCollector和Jsoup这类的工具,开发效率真的很高啊。
有18位网友表示赞同!
有没有什么教程可以介绍如何使用这两个库的?我也想学习一下网页抓取的相关知识。
有19位网友表示赞同!
我之前尝试过爬虫,感觉很多时候会遇到网站的防爬机制,这个方法好用吗?
有20位网友表示赞同!
这篇文章应该能帮我解决我在项目中遇到的分类图标获取的问题,期待能够顺利完成。
有17位网友表示赞同!
学习一下网页抓取技巧,可以帮助我也在数据分析方面更进一步啊!
有7位网友表示赞同!
想利用爬取的数据来做一些有趣的应用开发,这个方法看起来很有潜力啊!
有17位网友表示赞同!
现在很多电商网站的分类图标都设计得很有特点,能收集起来做个研究还挺有意思的。
有19位网友表示赞同!
我想用Python爬虫,不过Java也是很好的选择,看来我会学习学习WebCollector和Jsoup的使用了。
有12位网友表示赞同!
这篇文章解决了我的知识盲点,以前对这方面不太了解,现在终于可以开始尝试练习了!
有6位网友表示赞同!
对于数据分析来说,收集分类信息确实很重要,这个方法能够帮我节省很多时间和精力啊。
有13位网友表示赞同!
我很期待看到作者的后续文章,希望能详细讲解使用WebCollector和Jsoup抓取商品分类图标的过程。
有16位网友表示赞同!
学习新技能永远不会错!看看别人是怎么用Java爬虫进行数据获取的,也许会有新的灵感。
有17位网友表示赞同!
这篇文章提供的知识点非常实用,我应该可以把它应用到我的项目中改进代码效率。
有6位网友表示赞同!
我对网页抓取技术一直很感兴趣,这个帖子让我更想去深入学习了!&nb
有6位网友表示赞同!
我想要收集一些商品销售数据,不知道这个方法能不能用来获取商品的详细信息。
有10位网友表示赞同!