一、写在前面
(本专栏分为“java版微博爬虫”和“python版网络爬虫”两个项目,系列里所有文章将基于这两个项目讲解,项目完整源码已经整理到我的Github,有兴趣的可以去看下,链接地址在文末。)
网络爬虫根据需求的不同也分不同种类:
1.一种是爬取网页链接,通过url链接得到这个html页面中指定的链接,把这些链接存储起来,再依次以这些链接为源,再次爬取连接指向html页面中的链接……如此层层递归下去,常用的方法是广度优先或者深度优先,根据爬取层次需求不同而选择不同的方法达到最优效果,爬虫的效率优化是一个关键。
搜索引擎的第一个步骤就是通过爬虫得到需要索引的链接或数据,存放于数据库,然后对这些数据建立索引,然后定义查询语句,解析查询语句并利用检索器对数据库里的数据进行检索。
2.一种是爬取数据信息,如文本信息、图片信息等,有时需要做数据分析,通过某种手段来获取数据样本以供后续分析,常用的方法是爬虫获取指定数据样本或利用现有的公共数据库。本文的微博爬虫属于第二种类,根据自定义搜索关键字爬取微博信息数据。
对于网络爬虫原理,其实并不复杂。基本思路是:由关键字指定的url把所有相关的html页面全抓下来(html即为字符串),然后解析html文本(通常是正则表达式或者现成工具包如jsoup),提取微博文本信息,然后把文本信息存储起来。
重点在于对html页面源码结构的分析,不同的html需要不同的解析方法;还有就是长时间爬取可能对IP有影响,有时需要获取代理IP,甚至需要伪装浏览器爬取。
对于微博,通常情况下是必须登录才能看到微博信息数据(比如腾讯微博),但是有的微博有搜索机制,在非登录的情况下可以直接通过搜索话题来查找相关信息(如新浪微博、网易微博)。考虑到某些反爬虫机制,如果一个账号总是爬取信息可能会有些影响(比如被封号),所以本文采用的爬虫都是非登录、直接进入微博搜索页面爬取。这里关键是初始url地址。
二、网页分析
举个例子,对于有搜索机制的微博,如新浪微博和网易微博:(这里尤其要注意地址及参数!)
新浪微博搜索话题地址:http://s.weibo.com/weibo/苹果手机&nodup=1&page=50
网易微博搜索话题地址:http://t.163.com/tag/苹果手机
分别来看下网页截图和对应网页源码:
(1) 新浪微博:
(2) 网易微博:
我们需要做的就是把微博的文本提取出来,这里有些特征信息:根据关键字搜到的微博,其关键字会被标红,在html源码里有体现,分别查看两个网页的源代码。
可以看到,新浪微博的源码全部为html标签,为了反爬虫故意将源码混乱,并且汉字也做了处理,显示汉字的utf-8编码而不是直接显示汉字,不太好找,这里就需要查找红色字体color:red的部分,其中<span style=”color:red;”>u82f9u679cu624bu673a<span>中间夹着的哪些utf8编码其实就是关键字“苹果手机”。后面的那些utf8编码就是本条微博的文本内容。
而网易微博在这方面就要看起来容易许多,至少html里直接显示的是汉字,比较好找,而且微博文本部分其实是以json格式体现的,直接解析json就可以提取文本数据了,当然也可以直接用正则。
三、爬取微博
这里先写个简单的爬虫,原理都差不多,就拿网易微博为例,先说下爬虫程序需要用到的工具包:
httpclient-4.3.1.jar -------建立HTTP链接,用于从url获取html
httpcore-4.3.jar
httpmime-4.3.1.jar
httpclient-cache-4.3.1.jar
fluent-hc-4.3.1.jar
fastjson-1.1.41.jar ------解析json的工具包
jsoup-1.7.3.jar -------解析xml,html的工具包
dom4j-1.6.1.jar -------读写xml的工具包
commons-lang-2.1.jar
commons-logging-1.2.jar
commons-codec-1.8.jar
总体思路:
1. getHTML()方法:从url得到html字符串。
这里有两个关键点:
(1) 设置用户cookie策略,屏蔽掉cookie rejected的报错,当然可以不设置,直接用默认的client,即是CloseableHttpClient client = HttpClients.createDefault();创建的客户端,但是会报错;设置cookie的代码如下:
CookieSpecProvider cookieSpecProvider = new CookieSpecProvider(){ public CookieSpec create(HttpContext context){ return new BrowserCompatSpec(){ @Override public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException { //Oh, I am easy; } }; } }; Registry<CookieSpecProvider> r = RegistryBuilder .<CookieSpecProvider> create() .register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory()) .register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()) .register("easy", cookieSpecProvider) .build();
(2) 设置socket超时socketTimeout和连接超时connectTimeout,这很关键,如果不设置的话,当网络不好的情况下,某次请求没有及时得到响应,程序可能会卡死。但是设置连接超时,超时之后再自动重连就可以避免这个问题。代码如下:
RequestConfig requestConfig = RequestConfig.custom() .setCookieSpec("easy") .setSocketTimeout(5000) //设置socket超时时间 .setConnectTimeout(5000) //设置connect超时时间 .build();
2.isExitHTML()方法:
判断html是否合法(有效html,有微博内容的),有时候会出现页面不存在的情况,是因为该关键字没有微博信息,这是页面有提示:“没有找到相关的微博呢,换个关键词试试吧!”如下图;
3.writeWeibo2txt()方法:
正则解析(这里主要解析微博文本内容content、用户id、发文时间prettyTime),得到微博文本数据:控制台输出、写到txt文件;
(这里没有用jsoup去解析html,直接用的正则,有时间后面再写jsoup解析html的,比正则方便,当然对正则表达式这个工具掌握很熟练的朋友可以忽略……)
我的博客里另外有一篇正则教程完全总结,可以去看下:正则表达式总结
本来网易微博的源代码里嵌入了json元素,前面的html标签的都不是微博文本数据,在这个标签后面 <scriptid="data_searchTweet" type="application/json">才是json格式包括的微博文本,本来打算先把json串用正则匹配出来,后来发现,直接匹配关键字岂不更好?但这里要注意的是匹配的时候可能会有很多换行符之类的东西,我这里只匹配了三个field,正则表达式"id":s"d{19}",( *?)|(s*?)"content":s".*?",( *?)|(s*?)"prettyTime":s".*?"
顺便推荐一个检测正则的工具:http://tool.oschina.net/regex?optionGlobl=global把源码贴上来吧:
/** * @note 1.根据搜索关键词从指定url得到相应的html页面,并验证其合法性; * 2.得到微博样本:写到txt文件里 * * @author DianaCody * @since 2014-09-26 15:08 * */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.regex.Pattern; import java.util.regex.Matcher; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.cookie.Cookie; import org.apache.http.cookie.CookieOrigin; import org.apache.http.cookie.CookieSpec; import org.apache.http.cookie.CookieSpecProvider; import org.apache.http.cookie.MalformedCookieException; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.cookie.BestMatchSpecFactory; import org.apache.http.impl.cookie.BrowserCompatSpec; import org.apache.http.impl.cookie.BrowserCompatSpecFactory; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; public class Weibo163 { private String getHTML(String url) throws URISyntaxException, ClientProtocolException, IOException { //采用用户自定义cookie策略,不显示cookie rejected的报错 CookieSpecProvider cookieSpecProvider = new CookieSpecProvider(){ public CookieSpec create(HttpContext context){ return new BrowserCompatSpec(){ @Override public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException { } }; } }; Registry<CookieSpecProvider> r = RegistryBuilder .<CookieSpecProvider> create() .register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory()) .register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()) .register("cookie", cookieSpecProvider) .build(); RequestConfig requestConfig = RequestConfig.custom() .setCookieSpec("cookie") .setSocketTimeout(5000) //设置socket超时时间 .setConnectTimeout(5000) //设置connect超时时间 .build(); CloseableHttpClient httpClient = HttpClients.custom() .setDefaultCookieSpecRegistry(r) .setDefaultRequestConfig(requestConfig) .build(); HttpGet httpGet = new HttpGet(url); httpGet.setConfig(requestConfig); String html = "html获取失败"; //用于验证是否取到正常的html try{ CloseableHttpResponse response = httpClient.execute(httpGet); html = EntityUtils.toString(response.getEntity()); //System.out.println(html); //打印返回的html } catch (IOException e) { e.printStackTrace(); } return html; } private boolean isExistHTML(String html) throws InterruptedException { boolean isExist = false; Pattern pNoResult = Pattern.compile("\\u6ca1\\u6709\\u627e\\u5230\\u76f8" + "\\u5173\\u7684\\u5fae\\u535a\\u5462\\uff0c\\u6362\\u4e2a" + "\\u5173\\u952e\\u8bcd\\u8bd5\\u5427\\uff01"); //没有找到相关的微博呢,换个关键词试试吧!(html页面上的信息) Matcher mNoResult = pNoResult.matcher(html); if(!mNoResult.find()) { isExist = true; } return isExist; } private void writeWeibo2txt(String html, String savePath) throws IOException { File htmltxt = new File(savePath); //新建一个txt文件用于存放爬取的结果信息 FileWriter fw = new FileWriter(htmltxt); BufferedWriter bw = new BufferedWriter(fw); //regex-----"id":s"d{19}",( *?)|(s*?)"content":s".*?",( *?)|(s*?)"prettyTime":s".*?" Pattern p = Pattern.compile(""id":\s"\d{19}",(\n*?)|(\s*?)"content":\s".*?",(\n*?)|(\s*?)"prettyTime":\s".*?""); Matcher m = p.matcher(html); while(m.find()) { System.out.println(m.group()); bw.write(m.group()); } bw.close(); } public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException { Weibo163 crawler = new Weibo163(); String searchword = "iPad"; //搜索关键字为"iPad"的微博html页面 String html = crawler.getHTML("http://t.163.com/tag/"+searchword); String savePath = "e:/weibo/html.txt"; //输出到txt文件的存放路径 if(html != "html获取失败") { if(crawler.isExistHTML(html)) { System.out.println(html); crawler.writeWeibo2txt(html, savePath); } } //Pattern p = Pattern.compile("<script id="data_searchTweet" type="application/json">.+?<script>"); //<script id="data_searchTweet" type="application/json">.*?<script> //Matcher m = p.matcher(html); //html = crawler.getHTML("http://s.weibo.com/weibo/"+searchword+"&nodup=1&page="+1); //System.out.println(html); } }
程序运行截图:
该java爬虫项目源码github地址:https://github.com/DianaCody/Spider_SinaTweetCrawler_java。
原创文章,转载请注明出处:http://blog.csdn.net/dianacody/article/details/39584977
另外一个系列对爬虫的整理笔记:http://www.crifan.com/files/doc/docbook/web_scrape_emulate_login/release/html/web_scrape_emulate_login.html