作为一个宅男,每天看看美女图是必修课。那么——作为一个程序猿加宅男,如何收藏更多的美女图片呢?这就要用到爬虫了,哈哈,我仿佛看到了无穷无尽的美女在向我招手——怎么感觉写下这段话的时候自己略有一丝猥琐呢?啊呸,相当之猥琐!
我们的重点是学习写爬虫,嗯!
网络爬虫是做什么的?
他的主要工作就是 跟据指定的url地址 去发送请求,获得响应, 然后解析响应 , 一方面从响应中查找出想要查找的数据,另一方面从响应中解析出新的URL路径。
然后继续访问,继续解析;继续查找需要的数据和继续解析出新的URL路径
这就是网络爬虫主要干的工作. 下面是流程图:
通过上面的流程图 能大概了解到 网络爬虫 干了哪些活 ,根据这些 也就能设计出一个简单的网络爬虫出来。
一个简单的爬虫 必需的功能:
先看看运行效果
下面是包结构
核心代码
RequestAndResponseTool 类: 主要方法: 发送请求 返回响应 并把 响应 封装成 page 类
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 = 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;
}
}
page 类: 主要作用: 保存响应的相关内容 对外提供访问方法
/*
* 保存获取到的响应的相关内容;
*/
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: 类 主要作用 提供了 根据选择器来选取元素 属性 等方法
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<String> getLinks(Page page, String cssSelector) {
Set<String> links = new HashSet<String>();
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<String> getAttrs(Page page, String cssSelector, String attrName) {
ArrayList<String> result = new ArrayList<String>();
Elements eles = select(page, cssSelector);
for (Element ele : eles) {
if (ele.hasAttr(attrName)) {
result.add(ele.attr(attrName));
}
}
return result;
}
}
Links 类: 两个属性: 一个是存放 已经访问的url集合的set ; 一个是存放待访问url集合的 queue
/*
* Link主要功能;
* 存储已经访问过的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 接口: 可以起过滤作用
public interface LinkFilter {
public boolean accept(String url);
}