Java网络爬虫怎么实现?

Python021

Java网络爬虫怎么实现?,第1张

网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。\x0d\x0a传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。对于垂直搜索来说,聚焦爬虫,即有针对性地爬取特定主题网页的爬虫,更为适合。\x0d\x0a\x0d\x0a以下是一个使用java实现的简单爬虫核心代码:\x0d\x0apublic void crawl() throws Throwable { \x0d\x0awhile (continueCrawling()) { \x0d\x0aCrawlerUrl url = getNextUrl()//获取待爬取队列中的下一个URL\x0d\x0aif (url != null) { \x0d\x0aprintCrawlInfo() \x0d\x0aString content = getContent(url)//获取URL的文本信息\x0d\x0a \x0d\x0a//聚焦爬虫只爬取与主题内容相关的网页,这里采用正则匹配简单处理\x0d\x0aif (isContentRelevant(content, this.regexpSearchPattern)) { \x0d\x0asaveContent(url, content)//保存网页至本地\x0d\x0a\x0d\x0a//获取网页内容中的链接,并放入待爬取队列中\x0d\x0aCollection urlStrings = extractUrls(content, url)\x0d\x0aaddUrlsToUrlQueue(url, urlStrings)\x0d\x0a} else { \x0d\x0aSystem.out.println(url + " is not relevant ignoring ...")\x0d\x0a} \x0d\x0a\x0d\x0a//延时防止被对方屏蔽\x0d\x0aThread.sleep(this.delayBetweenUrls)\x0d\x0a} \x0d\x0a} \x0d\x0acloseOutputStream()\x0d\x0a}\x0d\x0aprivate CrawlerUrl getNextUrl() throws Throwable { \x0d\x0aCrawlerUrl nextUrl = null\x0d\x0awhile ((nextUrl == null) &&(!urlQueue.isEmpty())) { \x0d\x0aCrawlerUrl crawlerUrl = this.urlQueue.remove()\x0d\x0a//doWeHavePermissionToVisit:是否有权限访问该URL,友好的爬虫会根据网站提供的"Robot.txt"中配置的规则进行爬取 \x0d\x0a//isUrlAlreadyVisited:URL是否访问过,大型的搜索引擎往往采用BloomFilter进行排重,这里简单使用HashMap \x0d\x0a//isDepthAcceptable:是否达到指定的深度上限。爬虫一般采取广度优先的方式。一些网站会构建爬虫陷阱(自动生成一些无效链接使爬虫陷入死循环),采用深度限制加以避免 \x0d\x0aif (doWeHavePermissionToVisit(crawlerUrl) \x0d\x0a&&(!isUrlAlreadyVisited(crawlerUrl)) \x0d\x0a&&isDepthAcceptable(crawlerUrl)) { \x0d\x0anextUrl = crawlerUrl\x0d\x0a// System.out.println("Next url to be visited is " + nextUrl)\x0d\x0a} \x0d\x0a} \x0d\x0areturn nextUrl\x0d\x0a}\x0d\x0aprivate String getContent(CrawlerUrl url) throws Throwable { \x0d\x0a//HttpClient4.1的调用与之前的方式不同 \x0d\x0aHttpClient client = new DefaultHttpClient()\x0d\x0aHttpGet httpGet = new HttpGet(url.getUrlString())\x0d\x0aStringBuffer strBuf = new StringBuffer()\x0d\x0aHttpResponse response = client.execute(httpGet)\x0d\x0aif (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) { \x0d\x0aHttpEntity entity = response.getEntity()\x0d\x0aif (entity != null) { \x0d\x0aBufferedReader reader = new BufferedReader( \x0d\x0anew InputStreamReader(entity.getContent(), "UTF-8"))\x0d\x0aString line = null\x0d\x0aif (entity.getContentLength() >0) { \x0d\x0astrBuf = new StringBuffer((int) entity.getContentLength())\x0d\x0awhile ((line = reader.readLine()) != null) { \x0d\x0astrBuf.append(line)\x0d\x0a} \x0d\x0a} \x0d\x0a} \x0d\x0aif (entity != null) { \x0d\x0ansumeContent()\x0d\x0a} \x0d\x0a} \x0d\x0a//将url标记为已访问 \x0d\x0amarkUrlAsVisited(url)\x0d\x0areturn strBuf.toString()\x0d\x0a}\x0d\x0apublic static boolean isContentRelevant(String content, \x0d\x0aPattern regexpPattern) { \x0d\x0aboolean retValue = false\x0d\x0aif (content != null) { \x0d\x0a//是否符合正则表达式的条件 \x0d\x0aMatcher m = regexpPattern.matcher(content.toLowerCase())\x0d\x0aretValue = m.find()\x0d\x0a} \x0d\x0areturn retValue\x0d\x0a}\x0d\x0apublic List extractUrls(String text, CrawlerUrl crawlerUrl) { \x0d\x0aMap urlMap = new HashMap()\x0d\x0aextractHttpUrls(urlMap, text)\x0d\x0aextractRelativeUrls(urlMap, text, crawlerUrl)\x0d\x0areturn new ArrayList(urlMap.keySet())\x0d\x0a} \x0d\x0aprivate void extractHttpUrls(Map urlMap, String text) { \x0d\x0aMatcher m = (text)\x0d\x0awhile (m.find()) { \x0d\x0aString url = m.group()\x0d\x0aString[] terms = url.split("a href=\"")\x0d\x0afor (String term : terms) { \x0d\x0a// System.out.println("Term = " + term)\x0d\x0aif (term.startsWith("http")) { \x0d\x0aint index = term.indexOf("\"")\x0d\x0aif (index >0) { \x0d\x0aterm = term.substring(0, index)\x0d\x0a} \x0d\x0aurlMap.put(term, term)\x0d\x0aSystem.out.println("Hyperlink: " + term)\x0d\x0a} \x0d\x0a} \x0d\x0a} \x0d\x0a} \x0d\x0aprivate void extractRelativeUrls(Map urlMap, String text, \x0d\x0aCrawlerUrl crawlerUrl) { \x0d\x0aMatcher m = relativeRegexp.matcher(text)\x0d\x0aURL textURL = crawlerUrl.getURL()\x0d\x0aString host = textURL.getHost()\x0d\x0awhile (m.find()) { \x0d\x0aString url = m.group()\x0d\x0aString[] terms = url.split("a href=\"")\x0d\x0afor (String term : terms) { \x0d\x0aif (term.startsWith("/")) { \x0d\x0aint index = term.indexOf("\"")\x0d\x0aif (index >0) { \x0d\x0aterm = term.substring(0, index)\x0d\x0a} \x0d\x0aString s = //" + host + term\x0d\x0aurlMap.put(s, s)\x0d\x0aSystem.out.println("Relative url: " + s)\x0d\x0a} \x0d\x0a} \x0d\x0a} \x0d\x0a\x0d\x0a}\x0d\x0apublic static void main(String[] args) { \x0d\x0atry { \x0d\x0aString url = ""\x0d\x0aQueue urlQueue = new LinkedList()\x0d\x0aString regexp = "java"\x0d\x0aurlQueue.add(new CrawlerUrl(url, 0))\x0d\x0aNaiveCrawler crawler = new NaiveCrawler(urlQueue, 100, 5, 1000L, \x0d\x0aregexp)\x0d\x0a// boolean allowCrawl = crawler.areWeAllowedToVisit(url)\x0d\x0a// System.out.println("Allowed to crawl: " + url + " " + \x0d\x0a// allowCrawl)\x0d\x0acrawler.crawl()\x0d\x0a} catch (Throwable t) { \x0d\x0aSystem.out.println(t.toString())\x0d\x0at.printStackTrace()\x0d\x0a} \x0d\x0a}

能够做网络爬虫的编程语言很多,包括PHP、Java、C/C++、Python等都能做爬虫,都能达到抓取想要的数据资源。针对不同的环境,我们需要了解他们做爬虫的优缺点,才能选出合适的开发环境。

(一)PHP

网络爬虫需要快速的从服务器中抓取需要的数据,有时数据量较大时需要进行多线程抓取。PHP虽然是世界上最好的语言,但是PHP对多线程、异步支持不足,并发不足,而爬虫程序对速度和效率要求极高,所以说PHP天生不是做爬虫的。

(二)C/C++

C语言是一门面向过程、抽象化的通用程序设计语言,广泛应用于底层开发,运行效率和性能是最强大的,但是它的学习成本非常高,需要有很好地编程知识基础,对于初学者或者编程知识不是很好地程序员来说,不是一个很好的选择。当然,能够用C/C++编写爬虫程序,足以说明能力很强,但是绝不是最正确的选择。

(三)Java

在网络爬虫方面,作为Python最大的对手Java,拥有强大的生态圈。但是Java本身很笨重,代码量大。由于爬虫与反爬虫的较量是持久的,也是频繁的,刚写好的爬虫程序很可能就不能用了。爬虫程序需要经常性的修改部分代码。而Java的重构成本比较高,任何修改都会导致大量代码的变动。

(四)Python

Python在设计上坚持了清晰划一的风格,易读、易维护,语法优美、代码简洁、开发效率高、第三方模块多。并且拥有强大的爬虫Scrapy,以及成熟高效的scrapy-redis分布式策略。实现同样的爬虫功能,代码量少,而且维护方便,开发效率高。

下面说明知乎爬虫的源码和涉及主要技术点:

(1)程序package组织

(2)模拟登录(爬虫主要技术点1)

要爬去需要登录的网站数据,模拟登录是必要可少的一步,而且往往是难点。知乎爬虫的模拟登录可以做一个很好的案例。要实现一个网站的模拟登录,需要两大步骤是:(1)对登录的请求过程进行分析,找到登录的关键请求和步骤,分析工具可以有IE自带(快捷键F12)、Fiddler、HttpWatcher;(2)编写代码模拟登录的过程。

(3)网页下载(爬虫主要技术点2)

模拟登录后,便可下载目标网页html了。知乎爬虫基于HttpClient写了一个网络连接线程池,并且封装了常用的get和post两种网页下载的方法。

(4)自动获取网页编码(爬虫主要技术点3)

自动获取网页编码是确保下载网页html不出现乱码的前提。知乎爬虫中提供方法可以解决绝大部分乱码下载网页乱码问题。

(5)网页解析和提取(爬虫主要技术点4)

使用Java写爬虫,常见的网页解析和提取方法有两种:利用开源Jar包Jsoup和正则。一般来说,Jsoup就可以解决问题,极少出现Jsoup不能解析和提取的情况。Jsoup强大功能,使得解析和提取异常简单。知乎爬虫采用的就是Jsoup。 ...展开下面说明知乎爬虫的源码和涉及主要技术点:

(1)程序package组织

(2)模拟登录(爬虫主要技术点1)

要爬去需要登录的网站数据,模拟登录是必要可少的一步,而且往往是难点。知乎爬虫的模拟登录可以做一个很好的案例。要实现一个网站的模拟登录,需要两大步骤是:(1)对登录的请求过程进行分析,找到登录的关键请求和步骤,分析工具可以有IE自带(快捷键F12)、Fiddler、HttpWatcher;(2)编写代码模拟登录的过程。

(3)网页下载(爬虫主要技术点2)

模拟登录后,便可下载目标网页html了。知乎爬虫基于HttpClient写了一个网络连接线程池,并且封装了常用的get和post两种网页下载的方法。

(4)自动获取网页编码(爬虫主要技术点3)

自动获取网页编码是确保下载网页html不出现乱码的前提。知乎爬虫中提供方法可以解决绝大部分乱码下载网页乱码问题。

(5)网页解析和提取(爬虫主要技术点4)

使用Java写爬虫,常见的网页解析和提取方法有两种:利用开源Jar包Jsoup和正则。一般来说,Jsoup就可以解决问题,极少出现Jsoup不能解析和提取的情况。Jsoup强大功能,使得解析和提取异常简单。知乎爬虫采用的就是Jsoup。

(6)正则匹配与提取(爬虫主要技术点5)

虽然知乎爬虫采用Jsoup来进行网页解析,但是仍然封装了正则匹配与提取数据的方法,因为正则还可以做其他的事情,如在知乎爬虫中使用正则来进行url地址的过滤和判断。

(7)数据去重(爬虫主要技术点6)

对于爬虫,根据场景不同,可以有不同的去重方案。(1)少量数据,比如几万或者十几万条的情况,使用Map或Set便可;(2)中量数据,比如几百万或者上千万,使用BloomFilter(著名的布隆过滤器)可以解决;(3)大量数据,上亿或者几十亿,Redis可以解决。知乎爬虫给出了BloomFilter的实现,但是采用的Redis进行去重。

(8)设计模式等Java高级编程实践

除了以上爬虫主要的技术点之外,知乎爬虫的实现还涉及多种设计模式,主要有链模式、单例模式、组合模式等,同时还使用了Java反射。除了学习爬虫技术,这对学习设计模式和Java反射机制也是一个不错的案例。

4. 一些抓取结果展示收起