Jsoup+WebMagic+Selenium+phantomJs简易爬取房源信息网站内容并解析

好长时间没写博客了。

最近有个需求要一些房源平台的数据,看了看相关的网站数据再加上之前用Jsoup爬取过网站数据的经验觉得没什么问题。

于是用jsoup写了个简易demo进行某8数据爬取,没爬个几分钟呢就验证码了~

 

看来网站有反爬意识,毕竟靠数据资源吃饭的。 咦?浏览器看到的价位是正常的怎么爬取下来的html内容价位就是乱码呢?

因为自定义字体,每个详情页的字体都是动态加载的,关于加密字体解析的可以参考下面几个文章

https://www.cnblogs.com/a595452248/p/10800845.html

https://www.jianshu.com/p/a5d904c5d88e

仔细看了看html内容 发现在meta 标签的description里价位又写上正确的内容了,那我就不明白为什么要加密字体了(斜眼笑)

但猫眼就不一样了,所有数字都使用加密字体,无所谓是什么字体,把base64的内容下载下来存储成文件,在找出映射关系即可知道数字具体内容了。

 这里可以参考 Python版的 字体反爬处理

https://blog.csdn.net/xing851483876/article/details/82928607
 

后来在搜索资料的途中发现了webmagic这个插件,用起来也相当方便

webmagic项目地址 https://github.com/code4craft/webmagic 中文文档 http://webmagic.io/

以csdn论坛爬取为例,我要爬取论坛列表页面的html存储至本地,还有列表中的每条帖子的详情html内容存储至本地

DemoProcessor

package com.personal.secondhand.processor;

import com.personal.secondhand.pipeline.FileInfoPipeline;
import com.personal.secondhand.pipeline.FilePagePipeline;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

/**
 * csdn论坛爬取demo
 */
public class DemoProcessor implements PageProcessor {

    /**
     * 加入一些F12 debug到的请求头信息组装
     */
    private Site site = Site.me()
            .addHeader("F12查到的头信息key", "信息值")
            .setUserAgent("伪装的useragent")
            .setSleepTime(3000)
            .setRetryTimes(3)
            .setCycleRetryTimes(3);

    @Override
    public void process(Page page) {
        String html = page.getHtml().get();
        String url = page.getUrl().get();
        if (url.indexOf("page") != -1) {
            System.out.println("列表页");
            // 使用jsoup 解析html内容 并分析出每个帖子的详情链接
            Document document = Jsoup.parse(html);
            Elements elements = document.select("a[class=forums_title]");
            for (Element ele : elements) {
                String infoUrl = ele.attr("href");
                // 将列表页的详情url添加至任务中继续处理
                page.addTargetRequest(infoUrl);
            }
            // 将获取到的列表页html内容交由FilePagePipeline数据处理里
            page.putField("pageHtml",html);
        } else {
            System.out.println("详情页");
            // 将获取到的详情页html内容交由FileInfoPipeline数据处理里
            page.putField("infoHtml",html);
        }

    }

    public static void main(String[] args) {
        // 创建一个任务 处理
        Spider spider = Spider.create(new DemoProcessor());
        // 多个任务url
        spider.addUrl("https://bbs.csdn.net/forums/J2EE?page=1");
        spider.addUrl("https://bbs.csdn.net/forums/J2EE?page=2");
        // 将页面解析后的数据交给FileInfoPipeline/FilePagePipeline处理
        spider.addPipeline(new FileInfoPipeline("d:/csdnhtml/"));
        spider.addPipeline(new FilePagePipeline("d:/csdnhtml/"));
        // 开启多个线程
        spider.thread(4);
        // 启动
        spider.run();
    }


    @Override
    public Site getSite() {
        return site;
    }
}

FileInfoPipeline

package com.personal.secondhand.pipeline;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.FilePipeline;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
 * 公共的详情页面下载到本地
 * 只要接收到infoHtml即生成列表html页面
 * 文件命名替换(原命名可能无法找到具体访问url):
 *  (1)去除https://
 *  (2)?替换成#
 *  (3)/替换成_
 */
@Slf4j
public class FileInfoPipeline extends FilePipeline {

    public FileInfoPipeline() {
        super();
    }

    public FileInfoPipeline(String path) {
        super(path);
    }

    @Override
    public void process(ResultItems resultItems, Task task) {
        // 从PageProcess获取设置的html内容
        String html = resultItems.get("infoHtml");
        if (StringUtils.isBlank(html)) {
            // 没有就跳出
            return;
        }
        String url = resultItems.getRequest().getUrl();
        url = url.replaceAll("https://", "").replaceAll("\\?", "#").replaceAll("/", "_");
        // 文件命名为url路径,替换文件命名不符合的情况后形式如:
        // 以下内容参考至FilePipeline#process 替换了写入内容
        String today = new DateTime().toString("yyyyMMdd");
        String path = super.path + PATH_SEPERATOR + today + PATH_SEPERATOR + "infoHtml" + PATH_SEPERATOR + url;
        try {
//            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + ".html")), "UTF-8"));
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(path + ".html")), "UTF-8"));
            printWriter.println(html);
            printWriter.close();
        } catch (IOException e) {
            log.error("info html文件写入异常", e);
        }
    }
}

FilePagePipeline

package com.personal.secondhand.pipeline;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.FilePipeline;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
 * 公共列表页面html存储到本地
 * 只要接收到pageHtml即生成列表html页面
 * 文件命名替换(原命名可能无法找到具体访问url):
 *  (1)去除https://
 *  (2)?替换成#
 *  (3)/替换成_
 */
@Slf4j
public class FilePagePipeline extends FilePipeline {

    public FilePagePipeline() {
        super();
    }

    public FilePagePipeline(String path) {
        super(path);
    }

    @Override
    public void process(ResultItems resultItems, Task task) {
        // 从PageProcess获取设置的html内容
        String html = resultItems.get("pageHtml");
        if (StringUtils.isBlank(html)) {
            // 没有就跳出
            return;
        }
        String url = resultItems.getRequest().getUrl();
        url = url.replaceAll("https://", "").replaceAll("\\?", "#").replaceAll("/", "_");
        // 以下内容参考至FilePipeline#process 替换了写入内容
        String today = new DateTime().toString("yyyyMMdd");
        String path = super.path + PATH_SEPERATOR + today + PATH_SEPERATOR + "pageHtml" + PATH_SEPERATOR + url;
        try {
//            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + ".html")), "UTF-8"));
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(path + ".html")), "UTF-8"));
            printWriter.println(html);
            printWriter.close();
        } catch (IOException e) {
            log.error("page html文件写入异常", e);
        }
    }
}

执行main方法即可,在D盘的csdnhtml文件夹就出现了相关文件 打开是需要的html内容,将html文件用jsoup解析内容即可。

这样一个简单的数据爬取就完成了。

在Demo的process方法中,判断列表任务还是详情任务后解析html内容发给待处理的FileInfoPipeline和FilePagePipeline

对应的key才会去处理。官方demo有很挺多例子可以参考。

 

另外还有的网站是js动态链接跳转的页面,你无法使用jsoup或webmagic直接访问返回正确的html内容,如芒果房源

这时候需要selenium 和 phantomjs结合去处理了(phantomjs不在维护了,最新版的selenium 也不支持phantomjs了,如需替换可以使用Chrome或Firefox的headless)

代码在下面项目地址中

 

Jsoup就不多介绍了,主要是简单url访问及解析页面document。【也使用过过滤xss,因为踩坑印象挺深,当初某个版本(1.7.1?)解析过滤把一个正常字符串如(&orderby=xx)吃了部分并改变了一个字符串类似乱码(乱码字符+by)形式,导致参数错误。升级最新版就好了,但方法也改变了一些】

jsoup的解析语法大家可以自行搜索。

 

附上项目地址:https://github.com/UncleY/secondhand

 

另外各大网站对爬虫的限制都在根目录下的robots.txt声明,关于爬虫协议搜索引擎讲的比我详细。提一下是让大家尽量少消耗对方服务器资源。

先到这里~

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章