网络爬虫编写实践漫谈(一个集抓取与发布的爬虫程序的思路)

作者: 杨圣亮 分类: 网络爬虫 发布时间: 2016-12-09 21:36

在公司里,有两个人从事文字方面的工作,一个是文案,一位是行政人员,兼职做下编辑。可谓是编辑奇缺,一天只能写10篇左右的文章,效率不可谓之不低。究其原因,很多时间都花费在寻找资料上面了。

人虽少,事情还是得做,10多个网站等着编辑更新,按照目前的编辑文章发布速度,一天更新一个网站,得10天才能轮上一回,根本无法满足搜索引擎的胃口。为了把时间赢回来,我开始编写一个网络爬虫,顺便说一句,我现在在医疗行业,没有技术人员,勉为其难,咱得搞定它。

这个爬虫的设计和开发工作,全由我一人完成。它的目标是采集健康资讯类文章、药品、百科、及问答,种子网站是39健康网、99健康网等近10个大型健康门户,工作量确实挺大的,毕竟这么大的爬虫体系还是第一次搞,之前虽也用过python写过一些小型爬虫,但那太小了,只在一个网站里,而现在要做的工作,超过10倍都不止,这次用的是java和python混合开发,小伙伴们很关心,这个爬虫会开源吗?只能很抱歉,这项工作是在工作时间内做的,暂不提供开源版本,但会将遇到的问题分享一下,如有任何不足之处,也欢迎您的批评。

我的配置:

硬件部分:

勿需怀疑:爬虫这种完全依赖网络环境的程序,网速是真的是非常重要,于是建议,拉根像样的线(也就20MB电信),电脑配置嘛,稍微好些,I5,I7也差不多了,小公司单配服务器不现实,虽然服务器会更好,然而,从成本上来讲,你懂的,按下不说。

软件部分:

java1.8,虽说1.8不如1.7普及,还是向前看吧,甲骨文公司能把JAVA更新到1.8,肯定能说明比1.7要好那么一点。操作系统用的是:linux Mint,凭心而论,不喜欢ubuntu而喜欢debian,可能是由于折腾得太厉害吧,ubuntu崩溃几率太高,debian则坚如磐石,无奈的是博主用的是dell灵越系列的笔记本,无线驱动在debian怎么都搞不定,而ubuntu却可以。怎么办?最终选择了Linux Mint,是带有无线驱动的,只能妥协了,庆幸的是,它基于ubuntu却比ubuntu要稳定许多,用了一段时间,也没报错,就选它了。IDE的选择,折腾过netbeans、idea和eclipse,最终还是选择了eclipse,免费的还不错,虽有人说没有idea好,但idea是工花银子的,而且价格不菲,虽说网上也大牛分享了注册码,也有破解版,但作为有可能成为开发者的人来讲,版权意识还是得有的嘛。

第一关:反爬策略

爬虫是小偷,游走在别人的网站里,它不是搜索引擎,不是百度爬虫,它没有特权,网站站长想着怎么样封杀它、把内容藏起来,让爬虫找不到,而永远不会像对待百度spider那样,想尽一切办法把内容呈到它面前。也不能怪站长太现实,实际上,私有爬虫程序也的确是劣迹斑斑:它把被爬站的内容无偿拿走、增加服务器负担、更严重的爬虫会占据过多带宽,从而使被爬网站瘫痪,或者影响网站的正常运行。于是乎,站长们、服务器管理员会制定各种反爬虫策略

  1. 检查访问者header信息,是爬虫直接拒绝它的访问,别说抓取内容了,直接拒之门外;

  2. 限制单IP访客访问页面的频率,每分钟阅读超过10个页面的直接报错处理等等。

  3. 来源检查,不符合规范的直接pass掉;

  4. ……

总之,第一关就很不好过了,更别说那些需要登陆查看内容的、回复见内容的、内容用ajax加载的了。新技术的应用,使原本单纯的html页面变得愈来愈复杂了,可怜了爬虫开发人员,爬个站,真TMD不容易。第一关解题的关键是我得学会伪装,得像个浏览器,像个真实的访客,速度保持恰当的频率,“腰牌”上得甩掉爬虫标签,得像个真实的人,OK,过关了。进入网站后,就像到了仓库,各种“宝贝”享用不尽,别忙,进去了,还得解析网页,才能拿到想要的。

第二关:网站内容解析

破了第一关,原本还沾沾自喜,好景不长,第二关等着我呢,满屏的html,script,style交叉排列,文字信息全躲在里面了。要拿到可真不容易啊。好在有htmlparserhtmlunitjsoup之流,有这三件宝,可谓是利剑在手,一切我说了算。使用jsoup写个获取本站首页标题的例子。例1:

/*
* 作者:fedkey
* 网站: yangshengliang.com
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import org.jsoup.nodes.Element;

import org.jsoup.nodes.Document;

import org.jsoup.Jsoup;

public class GetBlogTitle {

    public static void main(String[] args) throws MalformedURLException {
        // 版本1
        String title = GetBlogTitle.getBlogTitle();
        System.out.println("版本1:"+title);
        //版本2
        String title2 = GetBlogTitle.getTitle();
        System.out.println("版本2:"+title2);
        
    }
    
    @SuppressWarnings("finally")
    public static String getTitle() {
        String url = "http://www.yangshengliang.com";
        String title = null;
        try {
            Document doc = Jsoup.connect(url).get();
            Element tmp = doc.getElementsByTag("title").get(0);
            title = tmp.text();
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            return title;
        }
    }
    public static String getBlogTitle() throws MalformedURLException {
        URL url = new URL("http://www.yangshengliang.com");
        String title = null; //标题
        try {
            StringBuffer buffer = new StringBuffer(); //放html结果
            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
            connection.connect();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line = null; 
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            Document doc = Jsoup.parse(buffer.toString()); // 创建可操作的jsoup Docuement对象
            Element tmp = doc.getElementsByTag("title").get(0); //获取title
            title = tmp.text();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        return title;
    }

}

都输出了正确的结果:

版本1:湖南SEO_杨圣亮博客_专注:网站优化/建设_网络营销工具开发
版本2:湖南SEO_杨圣亮博客_专注:网站优化/建设_网络营销工具开发

上面给出了两个方法,版本2 getTitle()方法明显比版本1getBlogTitle()简洁不少。想想,如果博客的服务器应用了反爬虫策略,要检查请求头,那么上面的代码肯定就不能达到预想的结果了,真实的情况比上面要复杂很多。好好啃htmlunit和jsoup就能帮助我们解决问题,对于那些ajax加载的页面,要获取它的内容,如果htmlunit不行,就只能 selenium了。

例2:获取一篇文章的内容。

还是拿博客开刀,url为:http://www.yangshengliang.com/seo-ji-shu/seo-jiaocheng/197.html

/*
 * 作者: fedkey
 * 网站: yangshengliang.com
 * url: http://www.yangshengliang.com/seo-ji-shu/seo-jiaocheng/197.html
 * 时间:2016-12-9
 */

import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class GetContentAndTitle {
    private static Document html = null; // 存储html源代码

    public static void main(String[] args) {
        String url = "http://www.yangshengliang.com/seo-ji-shu/seo-jiaocheng/197.html";
        GetContentAndTitle.getPageSource(url);
        String title = getTitle();
        System.out.println("title:"+title);
        String content = getContent();
        System.out.println("正文:"+content);

    }

    // 先来获取html源代码
    private static void getPageSource(String url) {
        try {
            html = Jsoup.connect(url).get();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    // 获取title
    private static  String getTitle() {
        // return html.title(); //返回title,过于简单,不使用,用getElementsByTag()方法获取。
        Element titleElement = html.getElementsByTag("title").get(0);
        String title = titleElement.text();
        return title;
    }
    // 获取正文
    private static String getContent() {
        // 通过分析网页html源码,得出的结果是div = "view-content" 的标签包裹的就是文章内容
        Element contentHtml = GetContentAndTitle.html.getElementsByClass("view-content").get(0); //只有一个class为view-content的标签,因此是第一个
        String content = contentHtml.text(); // 取到文字内容
        return content;
        
    }

}

运行代码,得到结果:

title:百度如何识别手机站和pc 站,加什么代码(已解决)_杨圣亮的个人网站
正文:问题如标题: 百度如何识别手机站和 PC 站?加什么代码啊?进入移动互联网之后,手机端流量超过电脑端,面对流量暴增的移动端,手机网站的SEO优化就不可忽视了,第一个问题来了。如何让百度(国内主要是百度,相信其他搜索引擎也会跟进)有效识别哪个是PC端,哪个是手机站? 其实搜索引擎是可以通过一定的代码检测方法来识别PC端与wap的问题,不过,为了避免出现失误。手动设置一下,还是很有必要的。 网站类型分三大类 1.PC站 2.手机站 3.响应式自适应网站 帮助搜索引擎(百度)识别 方法:在页面的<head>与</head>之间添加代码以下代码 PC站:<meta name=”applicable-device” content=”pc”> 手机站:<meta name=”applicable-device” content=”mobile” /> 响应式网页可标识:<meta name=”applicable-device”content=”pc,mobile”> 这样,百度就可以按照站长的设置,来区别对待PC或手机站了! 扩展知识: 在PC站网页里指定对应的手机站url,有些时候,PC站和手机站外观独立、域名不一样、共享数据库的情况下,PC站与手机站的URL几乎一样,只有微小的区别。比如:PC站URL为: www.abc.com/z/12.html 对应的手机站url为: m.abc.com/z/12.html 这种情况下,如果想要让搜索引擎主动将PC与手机站之间的页面一一对应起来。虽然在百度站长平台,已经有了【移动适配】,可以对应URL进行提交。 通过正则表达式可以达到全站适配的效果,不过,一像很懒的我,更愿意在网页的html中进行指定,这样的好处在于搜索引擎爬虫在处理PC端页面的时候,能够第一时间就和手机网页对应起来。同样是在html页面中的 head标签中插入代码,这次也是meta标签,这已经是除了关键字(keywords)与描述(description)之外的关于meta标签元素的用途了,可见:meta 标签对网站建设和seo的重要性可见一斑。当然,meta的用法还有很多,以后会写专门的文章来介绍,这里只说如果通过meta标签来指定与该页面对应的移动端url。 例: <meta name="mobile-agent" content="format=xhtml; url=http://m.bb.com/abc.html">
<meta name="mobile-agent" content="format=html5; url=http://m.bb.com/abc.html"> 例中的  url后面写移动端的url地址即可。

结果是正确 的,然而,得到纯文本其实并不是最好的,拿到数据后,通常是需要发布,没有了html格式,文字全堆在一起,是很难看的。常用的做法是选择性地去掉一些标签,如:script、style、a、div、<!–*–>等,如此一来,拿到的结果再次发布或使用浏览器查看,格式基本没变,是多么舒服的一件事,可见,让文字保持基本排版格式是多么重要啊。

第四关:数据存储

数据抓取到后,就需要存储起来,或写到硬盘里,或写入数据库中。数据存储要考虑效率和性能问题,毕竟多任务执行起来,多线程操作对数据存储也是个挑战,个人倾向于将文件直接写入txt中,然后编写发布模块,直接发布到相应地网站里。毕竟不是做API,没必要存储太多的数据。

第五关:URL列表维护

三个URL列表:已抓取URL列表、未抓取URL列表、失效URL列表。考虑到数据比较大,运算起来耗内存,全部读入内存不现实,很快就得报错。我在mysql数据库里分别建了三张表,分别存储相应的列表,运算起来性能也还不错。随着抓取的页面增加,这三个列表,肯定会越来越大,既要保证效率,又要准确,网友推荐了 布隆过滤器(Bloom Filter),确实是个了不起的算法实现。

第六关:内容去重

这是一项艰巨的任务,涉及到机器学习了,搜索引擎也一直在做这个任务(检查重复页面,是否原创),暂时没有过多的研究,只是在URL列表中,不抓取重复的URL,以达到最基本的去重复。

第七关:数据发布

数据发布应该是最简单的了,我是用来发布到网站的,通过编写代码模拟一下平时发布文章的过程,就能实现了。当然,也可以分析一下发布网站的程序,将数据直接推到数据中,也是可以了,灵活运用吧,偷懒,这一步,直接用python写了。

       
一条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

微信