每日博客
java爬虫
来自:Java爬虫之爬取数据_y……的博客-CSDN博客_java爬数据
pom.xml
<dependencies> <dependency> <groupId>com.googlecode.juniversalchardet</groupId> <artifactId>juniversalchardet</artifactId> <version>1.0.3</version> </dependency> <dependency> <groupId>org.kie.modules</groupId> <artifactId>org-apache-commons-httpclient</artifactId> <version>6.2.0.CR2</version> <type>pom</type> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.3</version> </dependency> </dependencies>
public interface LinkFilter { public boolean accept(String 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(); } }
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; } } }
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; } }
public static Page sendRequstAndGetResponse(String url) { Page page = null; // 1.生成 HttpClinet 对象并设置参数 HttpClient httpClient = new HttpClient(); // 设置 HTTP 连接超时 5s // 2.生成 GetMethod 对象并设置参数 GetMethod getMethod = new GetMethod(url); // 设置 get 请求超时 5s // 设置请求重试处理 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 class CharsetDetector { //从Nutch借鉴的网页编码检测代码 private static final int CHUNK_SIZE = 2000; private static Pattern metaPattern = Pattern.compile( "<meta\\s+([^>]*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( "<meta\\s+charset\\s*=\\s*[\"']?([a-z][_\\-0-9a-z]*)[^>]*>", 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; } } }
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 { DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(filePath))); // for (int i = 0; i < data.length; i++) { // out.write(data[i]); // } out.write(data); out.flush(); out.close(); System.out.println("文件:"+ fileName + "已经被存储在"+ filePath ); } catch (IOException e) { e.printStackTrace(); } } }
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; }
public void crawling(String[] seeds) { //初始化 URL 队列 initCrawlerWithSeeds(seeds); //定义过滤器,提取以 http://www.baidu.com 开头的链接 LinkFilter filter = new LinkFilter() { public boolean accept(String url) { if (url.startsWith("你想爬取的网站")) return true; else return false; } }; //循环条件:待抓取的链接不空且抓取的网页不多于 1000 while (!Links.unVisitedUrlQueueIsEmpty() && Links.getVisitedUrlNum() <= 1000) { //先从待访问的序列中取出第一个; String visitUrl = (String) Links.removeHeadOfUnVisitedUrlQueue(); if (visitUrl == null){ continue; } //根据URL得到page; Page page = RequestAndResponseTool.sendRequstAndGetResponse(visitUrl); //对page进行处理: 访问DOM的某个标签 Elements es = PageParserTool.select(page,"a"); if(!es.isEmpty()){ System.out.println("下面将打印所有a标签: "); System.out.println(es); } //将保存文件 FileTool.saveToLocal(page); //将已经访问过的链接放入已访问的链接中; Links.addVisitedUrlSet(visitUrl); //得到超链接 Set<String> links = PageParserTool.getLinks(page,"img"); for (String link : links) { Links.addUnvisitedUrlQueue(link); System.out.println("新增爬取路径: " + link); } } }