详解Java豆瓣电影爬虫——小爬虫成长记(附源码)

 更新时间:2016年12月12日 09:51:10   作者:JackieZheng  
这篇文章主要介绍了详解Java豆瓣电影爬虫——小爬虫成长记(附源码) ,具有一定的参考价值,有需要的可以了解一下。

以前也用过爬虫,比如使用nutch爬取指定种子,基于爬到的数据做搜索,还大致看过一些源码。当然,nutch对于爬虫考虑的是十分全面和细致的。每当看到屏幕上唰唰过去的爬取到的网页信息以及处理信息的时候,总感觉这很黑科技。正好这次借助梳理Spring MVC的机会,想自己弄个小爬虫,简单没关系,有些小bug也无所谓,我需要的只是一个能针对某个种子网站能爬取我想要的信息就可以了。有Exception就去解决,可能是一些API使用不当,也可能是遇到了http请求状态异常,又或是数据库读写有问题,就是在这个报exception和解决exception的过程中,JewelCrawler(儿子的小名)已经可以能够独立的爬取数据,并且还有一项基于Word2Vec算法做个情感分析的小技能。

后面可能还会有未知的Exception等着解决,也有一些性能需要优化,比如和数据库的交互,数据的读写等等。但是目测年内没有太多精力放这上面了,所以今天做一个简单的总结,而且前两篇主要侧重的是功能和结果,这篇来说说JewelCrawler是如何诞生的,并将代码放到Github上(源码地址在文章最后),有兴趣的可以关注下(仅供交流学习,请勿他用,考虑下douban君。多一点真诚,少一点伤害)

 环境介绍

开发工具:Intellij idea 14

数据库: Mysql 5.5 + 数据库管理工具Navicat(可用来连接查询数据库)

语言:Java

Jar包管理:Maven

版本管理:Git 

目录结构

其中

  com.ansj.vec是Word2Vec算法的Java版本实现

  com.jackie.crawler.doubanmovie是爬虫实现模块,其中又包括

有些包是空的,因为这些模块还没有用上,其中

  •     constants包是存放常量类
  •     crawl包存放爬虫入口程序
  •     entity包映射数据库表的实体类
  •     test包存放测试类
  •     utils包存放工具类

 resource模块存放的是配置文件和资源文件,比如

  •     beans.xml:Spring上下文的配置文件
  •     seed.properties:种子文件
  •     stopwords.dic:停用词库
  •     comment12031715.txt:爬取的短评数据
  •     tokenizerResult.txt:使用IKAnalyzer分词后的结果文件
  •     vector.mod:基于Word2Vec算法训练的模型数据

test模块是测试模块,用于编写UT.

 数据库配置

1. 添加依赖的包

JewelCrawler使用的maven管理,所以只需要在pom.xml中添加相应的依赖就可以了

<dependency>

  <groupId>org.springframework</groupId>

  <artifactId>spring-jdbc</artifactId>

  <version>4.1.1.RELEASE</version>

</dependency>

<dependency>

  <groupId>commons-pool</groupId>

  <artifactId>commons-pool</artifactId>

  <version>1.6</version>

</dependency>

<dependency>

  <groupId>commons-dbcp</groupId>

  <artifactId>commons-dbcp</artifactId>

  <version>1.4</version>

</dependency>

<dependency>

  <groupId>mysql</groupId>

  <artifactId>mysql-connector-java</artifactId>

  <version>5.1.38</version>

</dependency>

<dependency>

  <groupId>mysql</groupId>

  <artifactId>mysql-connector-java</artifactId>

  <version>5.1.38</version>

</dependency> 

2. 声明数据源bean

我们需要在beans.xml中声明数据源的bean

 <context:property-placeholder location="classpath*:*.properties"/>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

  <property name="driverClassName" value="${jdbc.driver}"/>

  <property name="url" value="${jdbc.url}"/>

  <property name="username" value="${jdbc.username}"/>

  <property name="password" value="${jdbc.password}"/>

</bean> 

注意: 这里是绑定了外部配置文件jdbc.properties,具体数据源的参数从该文件读取。

如果遇到问题“SQL [insert into user(id) values(?)]; Field 'name' doesn't  have a default value;”解决方法是设置表的相应字段为自增长字段。

解析页面遇到的问题

对于爬到的网页数据需要解析dom结构,拿到自己想要的数据,期间遇到如下错误

org.htmlparser.Node不识别

解决方法:添加jar包依赖

<dependency>

  <groupId>org.htmlparser</groupId>

  <artifactId>htmlparser</artifactId>

  <version>1.6</version>

</dependency> 

org.apache.http.HttpEntity不识别

解决方法:添加jar包依赖

<dependency>

  <groupId>org.apache.httpcomponents</groupId>

  <artifactId>httpclient</artifactId>

  <version>4.5.2</version>

</dependency> 

当然这是期间遇到的问题,最后用的是Jsoup做的页面解析。

 maven仓库下载速度慢

之前使用的是默认的maven中央仓库,下载jar包的速度很慢,不知道是我的网络问题还是其他原因,后来在网上找到了阿里云的maven仓库,更新后,相比之前简直是秒下,吐血推荐。

<mirrors>

  <mirror>

   <id>alimaven</id>

   <name>aliyun maven</name>

   <url>http://maven.aliyun.com/nexus/content/groups/public/</url>

   <mirrorOf>central</mirrorOf>    

  </mirror>

</mirrors> 

找到maven的settings.xml文件,添加这个镜像即可。

读取resource模块下文件的一种方法

比如读取seed.properties文件

@Test

  public void testFile(){

    File seedFile = new File(this.getClass().getResource("/seed.properties").getPath());

    System.out.print("===========" + seedFile.length() + "===========" );

  }

有关正则表达式

使用regrex正则表达式的时候,如果匹配上了定义的Pattern,则需要先调用matcher的find方法然后才能使用group方法找到子串。直接调用group方法是没有办法找到你想要的结果的。

  我看了下上面Matcher类的源码 

package java.util.regex;

import java.util.Objects;

public final class Matcher implements MatchResult {

  /**

   * The Pattern object that created this Matcher.

   */

  Pattern parentPattern;

 

  /**

   * The storage used by groups. They may contain invalid values if

   * a group was skipped during the matching.

   */

  int[] groups;

 

  /**

   * The range within the sequence that is to be matched. Anchors

   * will match at these "hard" boundaries. Changing the region

   * changes these values.

   */

  int from, to;

 

  /**

   * Lookbehind uses this value to ensure that the subexpression

   * match ends at the point where the lookbehind was encountered.

   */

  int lookbehindTo;

 

  /**

   * The original string being matched.

   */

  CharSequence text;

 

  /**

   * Matcher state used by the last node. NOANCHOR is used when a

   * match does not have to consume all of the input. ENDANCHOR is

   * the mode used for matching all the input.

   */

  static final int ENDANCHOR = 1;

  static final int NOANCHOR = 0;

  int acceptMode = NOANCHOR;

 

  /**

   * The range of string that last matched the pattern. If the last

   * match failed then first is -1; last initially holds 0 then it

   * holds the index of the end of the last match (which is where the

   * next search starts).

   */

  int first = -1, last = 0;

 

  /**

   * The end index of what matched in the last match operation.

   */

  int oldLast = -1;

 

  /**

   * The index of the last position appended in a substitution.

   */

  int lastAppendPosition = 0;

 

  /**

   * Storage used by nodes to tell what repetition they are on in

   * a pattern, and where groups begin. The nodes themselves are stateless,

   * so they rely on this field to hold state during a match.

   */

  int[] locals;

 

  /**

   * Boolean indicating whether or not more input could change

   * the results of the last match.

   *

   * If hitEnd is true, and a match was found, then more input

   * might cause a different match to be found.

   * If hitEnd is true and a match was not found, then more

   * input could cause a match to be found.

   * If hitEnd is false and a match was found, then more input

   * will not change the match.

   * If hitEnd is false and a match was not found, then more

   * input will not cause a match to be found.

   */

  boolean hitEnd;

 

  /**

   * Boolean indicating whether or not more input could change

   * a positive match into a negative one.

   *

   * If requireEnd is true, and a match was found, then more

   * input could cause the match to be lost.

   * If requireEnd is false and a match was found, then more

   * input might change the match but the match won't be lost.

   * If a match was not found, then requireEnd has no meaning.

   */

  boolean requireEnd;

 

  /**

   * If transparentBounds is true then the boundaries of this

   * matcher's region are transparent to lookahead, lookbehind,

   * and boundary matching constructs that try to see beyond them.

   */

  boolean transparentBounds = false;

 

  /**

   * If anchoringBounds is true then the boundaries of this

   * matcher's region match anchors such as ^ and $.

   */

  boolean anchoringBounds = true;

 

  /**

   * No default constructor.

   */

  Matcher() {

  }

 

/**

 * All matchers have the state used by Pattern during a match.

 */

Matcher(Pattern parent, CharSequence text) {

  this.parentPattern = parent;

  this.text = text;

 

  // Allocate state storage

  int parentGroupCount = Math.max(parent.capturingGroupCount, 10);

  groups = new int[parentGroupCount * 2];

  locals = new int[parent.localCount];

 

  // Put fields into initial states

  reset();

}

....

/**

 * Returns the input subsequence matched by the previous match.

 *

 * <p> For a matcher <i>m</i> with input sequence <i>s</i>,

 * the expressions <i>m.</i><tt>group()</tt> and

 * <i>s.</i><tt>substring(</tt><i>m.</i><tt>start(),</tt> <i>m.</i><tt>end())</tt>

 * are equivalent. </p>

 *

 * <p> Note that some patterns, for example <tt>a*</tt>, match the empty

 * string. This method will return the empty string when the pattern

 * successfully matches the empty string in the input. </p>

 *

 * @return The (possibly empty) subsequence matched by the previous match,

 *     in string form

 *

 * @throws IllegalStateException

 *     If no match has yet been attempted,

 *     or if the previous match operation failed

 */

public String group() {

  return group(0);

}

/**

 * Returns the input subsequence captured by the given group during the

 * previous match operation.

 *

 * <p> For a matcher <i>m</i>, input sequence <i>s</i>, and group index

 * <i>g</i>, the expressions <i>m.</i><tt>group(</tt><i>g</i><tt>)</tt> and

 * <i>s.</i><tt>substring(</tt><i>m.</i><tt>start(</tt><i>g</i><tt>),</tt> <i>m.</i><tt>end(</tt><i>g</i><tt>))</tt>

 * are equivalent. </p>

 *

 * <p> <a href="Pattern.html#cg">Capturing groups</a> are indexed from left

 * to right, starting at one. Group zero denotes the entire pattern, so

 * the expression <tt>m.group(0)</tt> is equivalent to <tt>m.group()</tt>.

 * </p>

 *

 * <p> If the match was successful but the group specified failed to match

 * any part of the input sequence, then <tt>null</tt> is returned. Note

 * that some groups, for example <tt>(a*)</tt>, match the empty string.

 * This method will return the empty string when such a group successfully

 * matches the empty string in the input. </p>

 *

 * @param group

 *     The index of a capturing group in this matcher's pattern

 *

 * @return The (possibly empty) subsequence captured by the group

 *     during the previous match, or <tt>null</tt> if the group

 *     failed to match part of the input

 *

 * @throws IllegalStateException

 *     If no match has yet been attempted,

 *     or if the previous match operation failed

 *

 * @throws IndexOutOfBoundsException

 *     If there is no capturing group in the pattern

 *     with the given index

 */

public String group(int group) {

  if (first < 0)

    throw new IllegalStateException("No match found");

  if (group < 0 || group > groupCount())

    throw new IndexOutOfBoundsException("No group " + group);

  if ((groups[group*2] == -1) || (groups[group*2+1] == -1))

    return null;

  return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();

}

/**

 * Attempts to find the next subsequence of the input sequence that matches

 * the pattern.

 *

 * <p> This method starts at the beginning of this matcher's region, or, if

 * a previous invocation of the method was successful and the matcher has

 * not since been reset, at the first character not matched by the previous

 * match.

 *

 * <p> If the match succeeds then more information can be obtained via the

 * <tt>start</tt>, <tt>end</tt>, and <tt>group</tt> methods. </p>

 *

 * @return <tt>true</tt> if, and only if, a subsequence of the input

 *     sequence matches this matcher's pattern

 */

public boolean find() {

  int nextSearchIndex = last;

  if (nextSearchIndex == first)

    nextSearchIndex++;

 

  // If next search starts before region, start it at region

  if (nextSearchIndex < from)

    nextSearchIndex = from;

 

  // If next search starts beyond region then it fails

  if (nextSearchIndex > to) {

    for (int i = 0; i < groups.length; i++)

      groups[i] = -1;

    return false;

  }

  return search(nextSearchIndex);

}

 

/**

 * Initiates a search to find a Pattern within the given bounds.

 * The groups are filled with default values and the match of the root

 * of the state machine is called. The state machine will hold the state

 * of the match as it proceeds in this matcher.

 *

 * Matcher.from is not set here, because it is the "hard" boundary

 * of the start of the search which anchors will set to. The from param

 * is the "soft" boundary of the start of the search, meaning that the

 * regex tries to match at that index but ^ won't match there. Subsequent

 * calls to the search methods start at a new "soft" boundary which is

 * the end of the previous match.

 */

boolean search(int from) {

  this.hitEnd = false;

  this.requireEnd = false;

  from    = from < 0 ? 0 : from;

  this.first = from;

  this.oldLast = oldLast < 0 ? from : oldLast;

  for (int i = 0; i < groups.length; i++)

    groups[i] = -1;

  acceptMode = NOANCHOR;

  boolean result = parentPattern.root.match(this, from, text);

  if (!result)

    this.first = -1;

  this.oldLast = this.last;

  return result;

}

...

} 

原因是这样的:这里如果不先调用find方法,直接调用group,可以发现group方法调用group(int group),该方法的方法体中有if first<0,显然这里这个条件是成立的,因为first的初始值就是-1,所以这里会抛异常。但是如果调用find方法,可以发现,最终会调用search(nextSearchIndex),注意这里的nextSearchIndex已被last赋值,而last的值为0,再跳转到search方法中

boolean search(int from) {

  this.hitEnd = false;

  this.requireEnd = false;

  from    = from < 0 ? 0 : from;

  this.first = from;

  this.oldLast = oldLast < 0 ? from : oldLast;

  for (int i = 0; i < groups.length; i++)

    groups[i] = -1;

  acceptMode = NOANCHOR;

  boolean result = parentPattern.root.match(this, from, text);

  if (!result)

    this.first = -1;

  this.oldLast = this.last;

  return result;

} 

这个nextSearchIndex传给了from,而from在方法体中被赋值给了first,所以,调用了find方法之后,这个的first就不在是-1,也就不是抛异常了。

源码已经上传至百度网盘:http://pan.baidu.com/s/1dFwtvNz

以上说的问题比较碎,都是在遇到问题和解决问题的时候的一些总结。在具体操作的时候还会遇到其他问题,有问题或者建议的话欢迎提出来^^。

最后放几张截止目前爬取的数据

Record表

其中存储的是79032条,爬取过的网页有48471条

movie表

目前爬取了2964部影视作品

comments表

爬取了29711条记录

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • java实现  微博登录、微信登录、qq登录实现代码

    java实现 微博登录、微信登录、qq登录实现代码

    这篇文章主要介绍了java实现 微博登录、微信登录、qq登录实现代码的相关资料,需要的朋友可以参考下
    2016-10-10
  • quartz的简单使用、SpringBoot使用和自定义数据源集成方式

    quartz的简单使用、SpringBoot使用和自定义数据源集成方式

    这篇文章主要介绍了quartz的简单使用、SpringBoot使用和自定义数据源集成方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教<BR>
    2024-01-01
  • IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    这篇文章主要介绍了IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 多模块项目使用枚举配置spring-cache缓存方案详解

    多模块项目使用枚举配置spring-cache缓存方案详解

    这篇文章主要为大家介绍了多模块项目使用枚举配置spring-cache缓存的方案详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • ElasticSearch合理分配索引分片原理

    ElasticSearch合理分配索引分片原理

    这篇文章主要介绍了ElasticSearch合理分配索引分片原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java设计模式之java模板方法模式详解

    Java设计模式之java模板方法模式详解

    这篇文章主要介绍了Java设计模式模板方法模式(Template)用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • JNDI简介_动力节点Java学院整理

    JNDI简介_动力节点Java学院整理

    这篇文章主要介绍了JNDI简介,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java中复杂的Synchronized关键字使用方法详解

    Java中复杂的Synchronized关键字使用方法详解

    Synchronized关键字是一个种锁,其有很多名字,例如重量级锁、悲观锁、可重入锁、、非公平、对象锁等等,这篇文章主要给大家介绍了关于Java中复杂的Synchronized关键字使用方法的相关资料,需要的朋友可以参考下
    2024-01-01
  • springboot整合logback打印日志,分文件

    springboot整合logback打印日志,分文件

    本文主要介绍了springboot整合logback打印日志,分文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11
  • Java自动生成编号的方法步骤

    Java自动生成编号的方法步骤

    在新增数据时,往往需要自动生成编号,本文主要介绍了Java自动生成编号的方法步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论