最近项目中有涉及到爬虫功能,目前做的还比较基础的,主要是在前人的一些公用方法的基础之上,对一些正则表达式的运用,还未涉及到性能以及反爬虫机制,先记录下来,此篇主要是想记录一些通用的网页匹配的工具类,后面做得好了再写后续。
爬虫:网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。(该释义来自百度百科,应该还比较好理解)
写爬虫之前,首先我们需要分析要爬取的URL和网页结构。
列表URL结构分析
如下图是我们要爬取的一系列网页数据,我们需要爬取每一篇文章的部分内容
它是在输入框输入“饮食 养生”关键词之后,呈现出的一个每页10条,一共100页的分页列表,我们再来看看每一页的URL的特点
我们看看每一页的网址:
第1页:
第2页:
第3页:
第100页:
我们可以很清晰地看出每一页的URL的规律,第1页的URL看起来似乎与其他有所不同,没有最后的&page=1,但实际上加上&page=1和不加都是一样的效果,因此我们在写正则表达式的时候只需把最后page=的“=”后面的数字匹配为1-100的整数即可。
网页结构分析
在网页上按F12查看网页源码,找到需要爬取部分的结构特点:
以上图来举例,我们需要爬取的部分是楼主发布的内容,如图中选中部分,右边就会显示该部分所在的网页结构,可以看到,每一楼发布的内容都是在一个”< tr> < /tr>”元素中
我们需要爬取的内容是第一个tr元素下的< div class=”content-body” >元素中,我们找的这个元素的特征一定要是唯一可识别的,在这个例子中,整个页面只有一个table元素,而每一个tr中也只有一个< div class=”content-body” >元素,因此我们定位在第一个tr元素下的class为content-body的div,这个是可以唯一识别的,分析完毕我们就可以开始写我们的爬虫规则了。
Links类
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
/*
* Link主要功能;
* 1: 存储已经访问过的URL路径 和 待访问的URL 路径;
*/
public class Links
{
// 已访问的 url 集合 已经访问过的 主要考虑 不能再重复了 使用set来保证不重复;
private static Set visitedUrlSet = new HashSet();
// 待访问的 url 集合 待访问的主要考虑 1:规定访问顺序;2:保证不提供重复的带访问地址;
private static LinkedList unVisitedUrlQueue = new LinkedList();
// 获得已经访问的 URL 数目
public static int getVisitedUrlNum()
{
return visitedUrlSet.size();
}
// 添加到访问过的 URL
public static void addVisitedUrlSet(String url)
{
visitedUrlSet.add(url);
}
// 移除访问过的 URL
public static void removeVisitedUrlSet(String url)
{
visitedUrlSet.remove(url);
}
// 获得 待访问的 url 集合
public static LinkedList getUnVisitedUrlQueue()
{
return unVisitedUrlQueue;
}
// 添加到待访问的集合中 保证每个 URL 只被访问一次
public static void addUnvisitedUrlQueue(String url)
{
if (url != null && !url.trim().equals("")
&& !visitedUrlSet.contains(url)
&& !unVisitedUrlQueue.contains(url))
{
unVisitedUrlQueue.add(url);
}
}
// 删除 待访问的url
public static Object removeHeadOfUnVisitedUrlQueue()
{
return unVisitedUrlQueue.removeFirst();
}
// 判断未访问的 URL 队列中是否为空
public static boolean unVisitedUrlQueueIsEmpty()
{
return unVisitedUrlQueue.isEmpty();
}
}
LinkFilter类:主要是判断输入的URL是否是我们想要的格式,后面代码一看就清楚了
public interface LinkFilter
{
public boolean accept(String url);
}
page包下的三个类
page类
import java.io.UnsupportedEncodingException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import com.youge.admin.crawl.util.CharsetDetector;
/*
* page
* 1: 保存获取到的响应的相关内容;
* */
public class Page
{
private byte[] content;
private String html; // 网页源码字符串
private Document doc;// 网页Dom文档
private String charset;// 字符编码
private String url;// url路径
private String contentType;// 内容类型
public Page(byte[] content, String url, String contentType)
{
this.content = content;
this.url = url;
this.contentType = contentType;
}
public String getCharset()
{
return charset;
}
public String getUrl()
{
return url;
}
public String getContentType()
{
return contentType;
}
public byte[] getContent()
{
return content;
}
/**
* 返回网页的源码字符串
*
* @return 网页的源码字符串
*/
public String getHtml()
{
if (html != null)
{
return html;
}
if (content == null)
{
return null;
}
if (charset == null)
{
charset = CharsetDetector.guessEncoding(content); // 根据内容来猜测 字符编码
}
try
{
this.html = new String(content, charset);
return html;
}
catch (UnsupportedEncodingException ex)
{
ex.printStackTrace();
return null;
}
}
/*
* 得到文档
*/
public Document getDoc()
{
if (doc != null)
{
return doc;
}
try
{
this.doc = Jsoup.parse(getHtml(), url);
return doc;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
}
PageParserTool类
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class PageParserTool {
/* 通过选择器来选取页面的 */
public static Elements select(Page page , String cssSelector) {
return page.getDoc().select(cssSelector);
}
/*
* 通过css选择器来得到指定元素;
*
* */
public static Element select(Page page , String cssSelector, int index) {
Elements eles = select(page , cssSelector);
int realIndex = index;
if (index < 0) {
realIndex = eles.size() + index;
}
return eles.get(realIndex);
}
/**
* 获取满足选择器的元素中的链接 选择器cssSelector必须定位到具体的超链接
* 例如我们想抽取id为content的div中的所有超链接,这里
* 就要将cssSelector定义为div[id=content] a
* 放入set 中 防止重复;
* @param cssSelector
* @return
*/
public static Set getLinks(Page page ,String cssSelector) {
Set links = new HashSet() ;
Elements es = select(page , cssSelector);
Iterator iterator = es.iterator();
while(iterator.hasNext()) {
Element 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"));
}
}
return links;
}
/**
* 获取网页中满足指定css选择器的所有元素的指定属性的集合
* 例如通过getAttrs("img[src]","abs:src")可获取网页中所有图片的链接
* @param cssSelector
* @param attrName
* @return
*/
public static ArrayList getAttrs(Page page , String cssSelector, String attrName) {
ArrayList result = new ArrayList();
Elements eles = select(page ,cssSelector);
for (Element ele : eles) {
if (ele.hasAttr(attrName)) {
result.add(ele.attr(attrName));
}
}
return result;
}
}
RequestAndResponseTool类
import java.io.IOException;
import java.net.URLEncoder;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class RequestAndResponseTool
{
public static Page sendRequstAndGetResponse(String url)
{
Page page = null;
// 1.生成 HttpClinet 对象并设置参数
HttpClient httpClient = new HttpClient();
// 设置 HTTP 连接超时 5s
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
// 2.生成 GetMethod 对象并设置参数
GetMethod getMethod;
if (url.contains("?"))
{
String[] tempUrl = url.split("\\?");
getMethod = new GetMethod(
tempUrl[0] + "?" + URLEncoder.encode(tempUrl[1]));
}
else
{
getMethod = new GetMethod(url);
}
// 设置 get 请求超时 5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
// 设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
// 3.执行 HTTP GET 请求
try
{
int statusCode = httpClient.executeMethod(getMethod);
// 判断访问的状态码
if (statusCode != HttpStatus.SC_OK)
{
System.err
.println("Method failed: " + getMethod.getStatusLine());
}
// 4.处理 HTTP 响应内容
byte[] responseBody = getMethod.getResponseBody();// 读取为字节 数组
String contentType = getMethod.getResponseHeader("Content-Type")
.getValue(); // 得到当前返回类型
page = new Page(responseBody, url, contentType); // 封装成为页面
}
catch (HttpException e)
{
// 发生致命的异常,可能是协议不对或者返回的内容有问题
System.out.println("Please check your provided http address!");
e.printStackTrace();
}
catch (IOException e)
{
// 发生网络异常
e.printStackTrace();
}
finally
{
// 释放连接
getMethod.releaseConnection();
}
return page;
}
public static Page sendRequstAndGetResponse(String url, String param)
{
Page page = null;
// 1.生成 HttpClinet 对象并设置参数
HttpClient httpClient = new HttpClient();
// 设置 HTTP 连接超时 5s
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
// 2.生成 GetMethod 对象并设置参数
String newParam = URLEncoder.encode(param);
GetMethod getMethod = new GetMethod(url + "?" + newParam);
// 设置 get 请求超时 5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
// 设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
// 3.执行 HTTP GET 请求
try
{
int statusCode = httpClient.executeMethod(getMethod);
// 判断访问的状态码
if (statusCode != HttpStatus.SC_OK)
{
System.err
.println("Method failed: " + getMethod.getStatusLine());
}
// 4.处理 HTTP 响应内容
byte[] responseBody = getMethod.getResponseBody();// 读取为字节 数组
String contentType = getMethod.getResponseHeader("Content-Type")
.getValue(); // 得到当前返回类型
page = new Page(responseBody, url, contentType); // 封装成为页面
}
catch (HttpException e)
{
// 发生致命的异常,可能是协议不对或者返回的内容有问题
System.out.println("Please check your provided http address!");
e.printStackTrace();
}
catch (IOException e)
{
// 发生网络异常
e.printStackTrace();
}
finally
{
// 释放连接
getMethod.releaseConnection();
}
return page;
}
}
util包下的几个类
CharsetDetector类
import org.mozilla.universalchardet.UniversalDetector;
import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 字符集自动检测
*
* @author hu
*/
public class CharsetDetector {
//从Nutch借鉴的网页编码检测代码
private static final int CHUNK_SIZE = 2000;
private static Pattern metaPattern = Pattern.compile(
"]*http-equiv=(\"|')?content-type(\"|')?[^>]*)>",
Pattern.CASE_INSENSITIVE);
private static Pattern charsetPattern = Pattern.compile(
"charset=\\s*([a-z][_\\-0-9a-z]*)", Pattern.CASE_INSENSITIVE);
private static Pattern charsetPatternHTML5 = Pattern.compile(
"]*>",
Pattern.CASE_INSENSITIVE);
//从Nutch借鉴的网页编码检测代码
private static String guessEncodingByNutch(byte[] content) {
int length = Math.min(content.length, CHUNK_SIZE);
String str = "";
try {
str = new String(content, "ascii");
} catch (UnsupportedEncodingException e) {
return null;
}
Matcher metaMatcher = metaPattern.matcher(str);
String encoding = null;
if (metaMatcher.find()) {
Matcher charsetMatcher = charsetPattern.matcher(metaMatcher.group(1));
if (charsetMatcher.find()) {
encoding = new String(charsetMatcher.group(1));
}
}
if (encoding == null) {
metaMatcher = charsetPatternHTML5.matcher(str);
if (metaMatcher.find()) {
encoding = 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;
}
}
}
HtmlRegexpUtil类(这是在网上百度的一个util类,有很多方法没有用上,在此基础上我作了一下改动,用上的也就一两个)
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* Title: HTML相关的正则表达式工具类
*
*
* Description: 包括过滤HTML标记,转换HTML标记,替换特定HTML标记
*
*
* Copyright: Copyright (c) 2006
*
*
* @author hejian
* @version 1.0
* @createtime 2006-10-16
*/
public class HtmlRegexpUtil
{
public final static String regxpForHtml = "<([^>]*)>"; // 过滤所有以<开头,以>结尾的标签
public final static String regxpForImgTag = "<\\s*img\\s*([^>]*)\\s*>"; // 找出IMG标签
public final static String regxpForImaTagSrcAttrib = "src=\"([^\"]+)\""; // 找出IMG标签的SRC属性
public final static String regxpForArticleTag = "<\\s*article\\s*([^>]*)\\s*>"; // 找出article标签
public final static String regxpForSpanTag = "<\\s*span\\s*([^>]*)\\s*>"; // 找出span标签
public final static String regxpForPTag = "<\\s*p\\s*([^>]*)\\s*>"; // 找出p标签
public final static String regxpForATag = "<\\s*a\\s*([^>]*)\\s*>"; // 找出a标签
public final static String regxpForStrongTag = "<\\s*strong\\s*([^>]*)\\s*>"; // 找出strong标签
/**
*
*/
public HtmlRegexpUtil()
{
// TODO Auto-generated constructor stub
}
/**
*
* 基本功能:替换标记以正常显示 (没用上)
*
*
* @param input
* @return String
*/
public String replaceTag(String input)
{
if (!hasSpecialChars(input))
{
return input;
}
StringBuffer filtered = new StringBuffer(input.length());
char c;
for (int i = 0; i <= input.length() - 1; i++)
{
c = input.charAt(i);
switch (c)
{
case '<':
filtered.append("<");
break;
case '>':
filtered.append(">");
break;
case '"':
filtered.append(""");
break;
case '&':
filtered.append("&");
break;
default:
filtered.append(c);
}
}
return (filtered.toString());
}
/**
*
* 基本功能:判断标记是否存在
*
*
* @param input
* @return boolean
*/
public boolean hasSpecialChars(String input)
{
boolean flag = false;
if ((input != null) && (input.length() > 0))
{
char c;
for (int i = 0; i <= input.length() - 1; i++)
{
c = input.charAt(i);
switch (c)
{
case '>':
flag = true;
break;
case '<':
flag = true;
break;
case '"':
flag = true;
break;
case '&':
flag = true;
break;
}
}
}
return flag;
}
/**
*
* 基本功能:过滤所有以"<"开头以">"结尾的标签 (没用上)
*
*
* @param str
* @return String
*/
public static String filterHtml(String str)
{
Pattern pattern = Pattern.compile(regxpForHtml);
Matcher matcher = pattern.matcher(str);
StringBuffer sb = new StringBuffer();
boolean result1 = matcher.find();
while (result1)
{
matcher.appendReplacement(sb, "");
result1 = matcher.find();
}
matcher.appendTail(sb);
return sb.toString();
}
/**
*
* 基本功能:过滤指定标签 (没用上)
*
*
* @param str
* @param tag
* 指定标签
* @return String
*/
public static String fiterHtmlTag(String str, String tag)
{
String regxp = "<\\s*" + tag + "\\s*([^>]*)\\s*>";
Pattern pattern = Pattern.compile(regxp);
Matcher matcher = pattern.matcher(str);
StringBuffer sb = new StringBuffer();
boolean result1 = matcher.find();
while (result1)
{
matcher.appendReplacement(sb, "");
result1 = matcher.find();
}
matcher.appendTail(sb);
return sb.toString();
}
/**
*
* 基本功能:替换指定的标签 (没用上)
*
*
* @param str
* @param beforeTag
* 要替换的标签
* @param tagAttrib
* 要替换的标签属性值
* @param startTag
* 新标签开始标记
* @param endTag
* 新标签结束标记
* @return String
* @如:替换img标签的src属性值为[img]属性值[/img]
*/
public static String replaceHtmlTag(String str, String beforeTag,
String tagAttrib, String startTag, String endTag)
{
String regxpForTag = "<\\s*" + beforeTag + "\\s+([^>]*)\\s*>";
String regxpForTagAttrib = tagAttrib + "=\"([^\"]+)\"";
Pattern patternForTag = Pattern.compile(regxpForTag);
Pattern patternForAttrib = Pattern.compile(regxpForTagAttrib);
Matcher matcherForTag = patternForTag.matcher(str);
StringBuffer sb = new StringBuffer();
boolean result = matcherForTag.find();
while (result)
{
StringBuffer sbreplace = new StringBuffer();
Matcher matcherForAttrib = patternForAttrib
.matcher(matcherForTag.group(1));
if (matcherForAttrib.find())
{
matcherForAttrib.appendReplacement(sbreplace,
startTag + matcherForAttrib.group(1) + endTag);
}
matcherForTag.appendReplacement(sb, sbreplace.toString());
result = matcherForTag.find();
}
matcherForTag.appendTail(sb);
return sb.toString();
}
/**
*
* @Title: removeAllTagAttr
* @Description: 移除指定标签的所有属性(我改动之后的方法)
* @author cjl
* @param content 源内容
* @param tagName 标签名称
* @return
* @throws
*/
public static String removeAllTagAttr(String content, String tagName)
{
String newContent = content.replaceAll(
"<" + tagName + "\\s[\\s\\S].*?>", "<" + tagName + ">");
return newContent;
}
/**
* 获取指定HTML标签的指定属性的值列表(我改动之后的方法)
* @param source 要匹配的源文本
* @param element 标签名称
* @param attr 标签的属性名称
* @return 属性值列表
*/
public static List getAttrContentList(String source, String element,
String attr)
{
// 匹配所有img标签
List result = new ArrayList();
String reg = "(<)" + element + "[\\s\\S].*?" + attr
+ "=\"([\\s\\S].*?)\"[\\s\\S].*?>";
Matcher m = Pattern.compile(reg).matcher(source);
while (m.find())
{
String r = m.group(2);
result.add(r);
}
return result;
}
/**
*
* @Title: removeImgWidthAttr
* @Description: 移除img标签的max-width或width属性(我改动之后的方法)
* @author cjl
* @param content
* @return
* @throws
* @date 2018-02-18 21:49
*/
public static String removeImgWidthAttr(String content)
{
// width="[\s\S].*?"|max-width="[\s\S].*?"
String newContent = content.replaceAll(
"width=\"[\\s\\S].*?\"|max-width=\"[\\s\\S].*?\"", "");
return newContent;
}
public static String removeImgStyleAttr(String content)
{
// width="[\s\S].*?"|max-width="[\s\S].*?"
String newContent = content.replaceAll("style=\"[\\s\\S].*?\"", "");
return newContent;
}
/**
*
* @Title: getAttrContent
* @Description: 找到指定属性值
* @param source
* @param element
* @param attr
* @return
* @throws
*/
public static String getAttrContent(String source, String element,
String attr)
{
// 匹配所有img标签
List result = new ArrayList();
String reg = "(<)" + element + "[\\s\\S].*?" + attr
+ "=\"([\\s\\S].*?)\"[\\s\\S]*.*?>";
Matcher m = Pattern.compile(reg).matcher(source);
while (m.find())
{
String r = m.group(2);
result.add(r);
}
return result.get(0);
}
public static void main(String[] args)
{
String source = "
";
System.out.println(HtmlRegexpUtil.getAttrContent(source, "img", "src"));
}
}
J1healthUtil类(这个类是根据我们项目的业务需求编写的特定的类)
package com.youge.admin.crawl.util;
import java.util.List;
import org.springframework.stereotype.Service;
/**
*
* @ClassName: JianYiUtil
* @Description: 健一健康(http://www.j1health.com)文章处理util
* @author cjl
* @date 2018年4月12日 下午2:27:52
*
*/
@Service
public class J1healthUtil
{
/**
*
* @Title: analyzeArticle
* @Description: 移除article、span标签,移除p、img(除了src)标签属性,img的url拼接完整
* @author cjl
* @param content
* @return
*/
public String analyzeArticle(String content)
{
HtmlRegexpUtil htmlRegexpUtil = new HtmlRegexpUtil();
String newContent = "";
// 1.移除div、span标签
if (htmlRegexpUtil.hasSpecialChars(HtmlRegexpUtil.regxpForArticleTag))
{
newContent = HtmlRegexpUtil.fiterHtmlTag(
HtmlRegexpUtil.fiterHtmlTag(content, "div"), "/div");
}
if (htmlRegexpUtil.hasSpecialChars(HtmlRegexpUtil.regxpForSpanTag))
{
newContent = HtmlRegexpUtil.fiterHtmlTag(
HtmlRegexpUtil.fiterHtmlTag(newContent, "span"), "/span");
}
// 2.移除p、img(除了src)、strong 标签属性;
if (htmlRegexpUtil.hasSpecialChars(HtmlRegexpUtil.regxpForPTag))
{
newContent = HtmlRegexpUtil.removeAllTagAttr(newContent, "p");
}
if (htmlRegexpUtil.hasSpecialChars(HtmlRegexpUtil.regxpForImgTag))
{
newContent = HtmlRegexpUtil.removeImgStyleAttr(newContent);
// 3.判断img的url是否需要拼接完整;
newContent = newContent.replaceAll("src=\"//", "src=\"http://");
}
if (htmlRegexpUtil.hasSpecialChars(HtmlRegexpUtil.regxpForStrongTag))
{
newContent = HtmlRegexpUtil.removeAllTagAttr(newContent, "strong");
}
return newContent.trim();
}
/**
*
* @Title: getImgSrcList
* @Description: 获取img的src属性值列表
* @author cjl
* @param newContent
* @return
* @date 2018-02-19 00:55
*/
public static List getImgSrcList(String newContent)
{
List imgSrcList = HtmlRegexpUtil.getAttrContentList(newContent,
"img", "src");
return imgSrcList;
}
}
以上是用到的一些辅助类,下面是爬取逻辑的代码,我省略了一些具体的业务上的代码,具体你要爬取哪些内容取决于你的需求,主要是看一下解析网页提取需要的元素部分,我这里也只是作为参考(注释中的一级表也就是前面说的所有列表的URL,二级表则是每个URL对应的网页数据表)
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.youge.admin.crawl.link.LinkFilter;
import com.youge.admin.crawl.link.Links;
import com.youge.admin.crawl.page.Page;
import com.youge.admin.crawl.page.PageParserTool;
import com.youge.admin.crawl.page.RequestAndResponseTool;
import com.youge.admin.crawl.util.J1healthUtil;
import com.youge.admin.crawl.util.SouhuUtil;
import com.youge.service.pg.article.ArticleContentService;
import com.youge.service.pg.article.ArticleUrlService;
import com.youge.service.pg.article.ChannelSpiderKeywordsService;
import com.youge.service.pg.article.ChannelSpiderKeywordsSourceService;
import com.youge.util.IdWorker;
/**
*
* @ClassName: J1healthCrawler
* @Description: 健一健康爬虫
* @author cjl
* @date 2018年4月12日 上午12:04:48
*
*/
@Service
public class J1healthCrawler
{
@Autowired
private ArticleUrlService articleUrlService;
@Autowired
private ArticleContentService articleContentService;
@Autowired
private J1healthUtil jianYiUtil;
@Resource
private ChannelSpiderKeywordsService channelSpiderKeywordsService;
@Resource
private ChannelSpiderKeywordsSourceService channelSpiderKeywordsSourceService;
/**
* 使用种子初始化 URL 队列
*
* @param seeds 种子 URL
* @return
*/
private void initCrawlerWithSeeds(String[] seeds)
{
for (int i = 0; i < seeds.length; i++)
{
Links.addUnvisitedUrlQueue(seeds[i]);
}
}
/**
*
* @Title: crawl
* @Description: 抓取
* @author cjl
* @param seeds
* @date 2018-02-11 10:30
*/
public void crawl(String[] seeds)
{
// 1.初始化url队列
initCrawlerWithSeeds(seeds);
// 2.过滤http开头的url
@SuppressWarnings("unused")
LinkFilter filter = new LinkFilter()
{
public boolean accept(String url)
{
if (url.startsWith("http://www.j1health.com/search"))
return true;
else
return false;
}
};
// 3.通过url得到网页内容
while (!Links.unVisitedUrlQueueIsEmpty())
{
// 从种子队列中先取第一个url
String visitUrl = (String) Links.removeHeadOfUnVisitedUrlQueue();
if (visitUrl == null)
{
continue;
}
// 根据URL得到page;
Page page = RequestAndResponseTool
.sendRequstAndGetResponse(visitUrl);
// 对page进行处理: 访问DOM的某个标签
// 4.分析网页内容,得到文章列表,存入数据库
Elements titleElements = PageParserTool.select(page,
"ul.search-list div.search-item-title a");
List articleUrls = new ArrayList();
List titles = new ArrayList();
// 文章url一级表信息存库
Map articleUrlMap = new HashMap();
if (!titleElements.isEmpty())
{
titles = titleElements.eachText();
articleUrls = titleElements.eachAttr("href");
}
// 取单条记录
for (int i = 0; i < titles.size(); i++)
{
articleUrlMap.put("title", titles.get(i));
articleUrlMap.put("articleUrl", articleUrls.get(i));
articleUrlMap.put("source", "健一健康");
articleUrlService.addArticleUrl(articleUrlMap);
}
// 将访问过的url放入已访问队列中
Links.addVisitedUrlSet(visitUrl);
}
}
/**
*
* @Description: 根据文章一级表信息抓取二级表信息
* @author cjl
* @date 2018-04-12 17:41
*/
public void crawlArticleContent()
{
Map paramsMap = new HashMap();
paramsMap.put("isAnalysed", 0);
// 1.查询所有未被解析过的文章信息
// 从一级表得到id,url,title,source,根据url得到文章content,存入二级表
cn.magicbeans.pagination.Page
我们在调用的时候传入种子URL队列即可,比如:
@Controller
@RequestMapping("/j1healthCrawler")
public class CrawlerJ1healthController
{
@Resource
private J1healthCrawler j1healthCrawler;
/**
*
* @Description: 爬取
* @author cjl
*/
@RequestMapping("/crawl")
@ResponseBody
public void crawl(String[] seeds)
{
String[] seeds2 = new String[100];
int j = -1;
//种子URL队列
for (int i = 1; i <= 100; i++)
{
seeds2[++j] = "http://www.j1health.com/search/topic?keyword=饮食 养生&page="
+ i;
}
j1healthCrawler.crawl(seeds2);
j1healthCrawler.crawlArticleContent();
}
至此,一个简单的爬虫基本完成,在此过程中,有一个需要注意的细节,如下图,我们在访问URL的时候,需要对URL中的中文进行处理,否则会报错
这个爬虫的例子里面,能够通用的应该就是提取元素的部分了,但还是要根据具体的业务需求来定制自己的util类,最后的种子URL队列本来是准备写成正则来表示1-100的整数数字,不过没写出来,因为赶时间,就写成了这种笨办法来实现。