-
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中添加相应的依赖就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
< 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
1
2
3
4
5
6
7
|
< 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包依赖
1
2
3
4
5
|
< dependency > < groupId >org.htmlparser</ groupId > < artifactId >htmlparser</ artifactId > < version >1.6</ version > </ dependency > |
org.apache.http.HttpEntity不识别
解决方法:添加jar包依赖
1
2
3
4
5
|
< dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > < version >4.5.2</ version > </ dependency > |
当然这是期间遇到的问题,最后用的是Jsoup做的页面解析。
maven仓库下载速度慢
之前使用的是默认的maven中央仓库,下载jar包的速度很慢,不知道是我的网络问题还是其他原因,后来在网上找到了阿里云的maven仓库,更新后,相比之前简直是秒下,吐血推荐。
1
2
3
4
5
6
7
8
|
< 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文件
1
2
3
4
5
|
@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类的源码
原因是这样的:这里如果不先调用find方法,直接调用group,可以发现group方法调用group(int group),该方法的方法体中有if first<0,显然这里这个条件是成立的,因为first的初始值就是-1,所以这里会抛异常。但是如果调用find方法,可以发现,最终会调用search(nextSearchIndex),注意这里的nextSearchIndex已被last赋值,而last的值为0,再跳转到search方法中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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,也就不是抛异常了。
源码已经上传至Github:https://github.com/DMinerJackie/JewelCrawler
以上说的问题比较碎,都是在遇到问题和解决问题的时候的一些总结。在具体操作的时候还会遇到其他问题,有问题或者建议的话欢迎提出来^^。
最后放几张截止目前爬取的数据
Record表
其中存储的是79032条,爬取过的网页有48471条
movie表
目前爬取了2964部影视作品
comments表
爬取了29711条记录
出处:https://www.cnblogs.com/bigdataZJ/p/doubanmovie3.html