Java爬虫及分布式部署

基于HttpClient爬虫

环境

  • IDEA 2017.2
  • JDK 1.8
  • httpclient 4.5.4
  • maven 3.5.0

基本步骤

1.在maven中导入httpClient依赖    
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
        <version>4.5.4</version>
</dependency>
2. 发送请求并接受数据
   2.1 发送get请求 
package com.vking.httpClient;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;

/**
 * @author 待你如初见
 * @create 2018-09-25 9:59
 **/
public class HTTPClientGet {

    public static void main(String[] args) throws IOException {
        //1. 需要先创建httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //2. 指定请求方式
        HttpGet get = new HttpGet("http://www.jd.com");

        //3. 可选的: 封装请求参数
        //3.1 封装请求体
        get.setHeader("user-agent","Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36");

        //4. 发送请求
        // CloseableHttpResponse: 封装了响应的所有的内容: 响应行 响应头 响应体
        CloseableHttpResponse response = httpClient.execute(get);

        //5. 获取数据
        //5.1 获取状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println(statusCode);

        if(statusCode==200){
            //获取响应体的数据
            //在httpClient工具包中已经提供了获取响应体的快捷的方式
            String html = EntityUtils.toString(response.getEntity(), "UTF-8");
            System.out.println(html);

        }
    }
}
2.2 发送post请求
package com.vking.httpClient;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 待你如初见
 * @create 2018-09-25 10:12
 **/
public class HttpClientPost {

    public static void main(String[] args) throws Exception {
        //1. 获取httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //2. 指定请求方式
        HttpPost httpPost = new HttpPost("http://www.jd.com");

        //3. 封装请求参数
        List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();

        list.add(new BasicNameValuePair("username","xiaochuan"));
        list.add(new BasicNameValuePair("age","48"));

        HttpEntity entity = new UrlEncodedFormEntity(list);
        httpPost.setEntity(entity);
        //3.1 请求体

        //4. 执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        //5. 获取数据
        //5.1 获取状态码
        int statusCode = response.getStatusLine().getStatusCode();
        //5.2 获取响应头的数据
        Header[] headers = response.getHeaders("Content-Type");

        String value = headers[0].getValue();
        System.out.println(statusCode +"  "+ value);

        //5.3 获取响应体数据
        String html = EntityUtils.toString(response.getEntity(), "UTF-8");
        System.out.println(html);
    }
}

解析数据(HTML)

jsoup

jsoup是一款专门用来在java端来解析HTML的工具包, HTML文档其实就是一个DOM对象, 所以如果要使用jsoup首先需要先获取到文档的Dom对象

1.导入依赖
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.3</version>
</dependency>
  • jsoup获取dom对象的方式
package com.vking.jsoup;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.File;
import java.io.IOException;

/**
 * @author 待你如初见
 * @create 2018-09-25 10:55
 **/
public class JsoupDom {

    public static void main(String[] args) throws IOException {
        //1. 最常用的一种方式
        String html = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>获取html文档的第一种方式</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "\n" +
                "</body>\n" +
                "</html>";
        Document document1 = Jsoup.parse(html);
        String title = document1.title();
        System.out.println(title);

        //2. 可以通过发送一个HTTP请求获取dom对象
       /* Document document2 = Jsoup.connect("http://www.jd.com").get();
        System.out.println(document2);*/

        //3. 加载一个外部的HTML文件
        //Document document3 = Jsoup.parse(new File(""), "utf-8");

        //4. 根据给定的HTML 代码片段来获取dom对象
        String html2 = "<a>第四种获取dom对象的方式</a>";
        Document document4 = Jsoup.parseBodyFragment(html2);
        //Document document4 = Jsoup.parse(html2);
        String text = document4.text();
        System.out.println(text);
    }
}

在jsoup中一共提供了两套API:

  • 一套是采用原生的js的方式来获取dom中元素
    • 需要程序员记住大量的js的原生的方法, 会大大提升学习的成本
  • 一套是基于Css的选择器来完成解析:(常用的一种方案)
    • 常用的选择器: id选择器 类选择器 元素选择器 层级选择器 属性选择

jsoup常用的方法:

  • 静态方法: parse(String html)
  • select(选择器);
  • text(); 获取文本内容
  • html();获取连html的代码一并获取过来
  • attr(String name); 获取元素的属性

模拟爬取京东商品数据

这里采用多线程加阻塞队列方式多线程和队列的以后补充package com.vking.spider;import com.google.gson.Gson; import com.vking.ProductDao; import com.vking.pojo.Product; import com.vking.utils.HttpClientUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements;import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;/** * @author 待你如初见 * @create 2018-09-25 10:12 **/ public class JDSpider { // ProductDao为是数据库链接方式这里采用Spring jdbcTemple private static ProductDao productDao = new ProductDao(); // 创建容量为一千的队列 private static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1000); // 创建固定数量线程池 private static ExecutorService threadPool = Executors.newFixedThreadPool(51);public static void main(String[] args) throws Exception { // 用于查看队列剩余 threadPool.execute(new Runnable() { public void run() { while(true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前剩余" + queue.size()); } } }); // 优先开启数据写入线程 // 如果先开启page会出现无法写入问题 toothread(); // 开起队列写入 page(); } public static Product skuContent(String skuId) throws IOException { // 创建Product类 Product product = new Product(); // set productid product.setPid(skuId); // 获取商品详情页 String url = "https://item.jd.com/" + skuId + ".html"; // set ProductUrl product.setUrl(url); // 获取商品详情页THML String html = HttpClientUtils.doGet(url); // 使用Jsoup Document document = Jsoup.parse(html); // 获取title Elements skuName = document.select(".sku-name"); product.setTitle(skuName.text()); // 获取品牌 Elements brand = document.select("#parameter-brand>li"); product.setBrand(brand.attr("title")); // 获取商品名 Elements pname = document.select("[class=\"parameter2 p-parameter-list\"] li:first-child"); product.setPname(pname.attr("title")); // 获取价格 // 可以采用获取商品列表页的价格或者商品详情页发送AJAX 方式自行选取这里采用详情页发送AJAX // 价格获取需要AJAX请求所以需要单独发送请求 String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J\_" + skuId; // 获取价格json字符串 String pJSON = HttpClientUtils.doGet(priceUrl); // 这里使用Gosn处理返回的JSON Gson gson = new Gson(); List<Map<String, String>> list = gson.fromJson(pJSON, List.class); String price = list.get(0).get("p"); // set Price product.setPrice(price); return product; } public static void addProduct(Product product) { //存入数据库 productDao.addProducr(product); } public static void page() throws IOException, InterruptedException { // 循环获取100页内容 for (int i = 0; i < 100; i++) { //https://search.jd.com/Search?keyword=iphone%208%20plus&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&bs=1&ev=exbrand\_Apple%5E&page=3&s=61&click=0 String url = "https://search.jd.com/Search?keyword=手机&enc=utf-8&pvid=3ae312e430f94a798fd95b1a94b9bd4e&page=" + (2 \* i - 1); //获取商品列表的每一页 String html = HttpClientUtils.doGet(url); Thread.sleep(200); Document document = Jsoup.parse(html); Elements lis = document.select("#J\_goodsList>ul>li"); for (Element li : lis) { //获取商品id String sku\_id = li.attr("data-sku"); queue.put(sku\_id);// Product product = skuContent(sku_id); // // System.out.println(product); // // addProduct(product); } // 获取商品价格 另一种价格获取方式 // Elements sku\_price = li.select(".J\_" + sku\_id + ">i"); } } public static void toothread() { //开启50线程写入数据库 for (int i = 0; i < 50; i++) { threadPool.execute(new Runnable() { public void run() { while (true) { try { String sku\_id = queue.take(); Product product = skuContent(sku\_id); System.out.println(product); addProduct(product); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }); } }}

分布式爬虫部署

导入maven打包插件

<build>
<plugins>
    <!--这是jdk编译的插件 -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>utf-8</encoding>
        </configuration>
    </plugin>
    <!--打包的插件-->
    <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
            <archive>
                <manifest> <!-- 注意 此为设置程序的主入口-->
                    <mainClass>com.itheima.spider.JDSpiderMaster</mainClass>
                </manifest>
            </archive>
            <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
        </configuration>
    </plugin>
</plugins>
</build>

设计master代码

package com.vking.jdSpider;

import com.vking.jedis.utils.JedisUtils;

import com.vking.utils.HttpClientUtils;

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import org.jsoup.nodes.Element;

import org.jsoup.select.Elements;

import redis.clients.jedis.Jedis;

/**

- master程序是用来根据url获取pid的
- 
- @author 带你如如初见
- @create 2018-09-25 11:45
**/
public class JdMaster {

private static Jedis jedis = JedisUtils.getJedis();

public static void main(String[] args) throws Exception {

    page();

}

public static void page() throws Exception {

    for (int i = 1; i <= 100; i++) {

        String pageUrl = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&page=" + (2 * i - 1);

        // 1. 发起请求, 获取数据
        String html = HttpClientUtils.doGet(pageUrl);

        // 2. 解析数据
        parsePid(html);

    }

}

//此方法用来解析pid
private static void parsePid(String html) throws Exception {

    //1. 获取dom对象

    Document document = Jsoup.parse(html);

    // 2. 解析pid

    Elements liEl = document.select("#J_goodsList>ul>li");

    // 3. 从li中获取pid

    for (Element li : liEl) {

        String pid = li.attr("data-pid");

       /* Product product = parseProduct(pid);

        System.out.println(product);

        //4. 数据保存的操作


        productDao.addProduct(product);*/

        //将pid填充到阻塞队列中

        //queue.put(pid);

        //将pid保存到redis中

        jedis.lpush("bigData:jdSpider:pid", pid);

        //jedis.close();

    }



}


}

slave

import com.google.gson.Gson;

// 存入数据库类

import com.vking.dao.ProductDao;

// jedis工具类

import com.vking.jedis.utils.JedisUtils;

// pojo product类

import com.vking.pojo.Product;

// httpClient工具类

import com.vking.utils.HttpClientUtils;

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import org.jsoup.select.Elements;

import redis.clients.jedis.Jedis;

import java.io.IOException;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


- slave程序主要 用来获取pid, 解析商品数据
- @author 待你如初见
- @create 2018-09-25 11:51

public class JdSlave {
    private static ExecutorService threadPool = Executors.newFixedThreadPool(31);
    private static ProductDao productDao = new ProductDao();


public static void main(String[] args) {

    tooThread();

}

public static void  tooThread(){

    for(int i = 0 ; i<30 ; i++){

        threadPool.execute(new Runnable() {

            public void run() {

                // 1. 从队列中获取数据

                while(true) {

                    try {

                       // String pid = queue.take();

                        // 从redis中获取pid

                        Jedis jedis = JedisUtils.getJedis();


                        List<String> list = jedis.brpop(0, "bigData:jdSpider:pid");

                        jedis.close();

                        //2. 根据pid获取数据

                        Product product = parseProduct(list.get(1));
                        //3. 保存数据

                        productDao.addProduct(product);

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                }

            }

        });

    }

}
private static Product parseProduct(String pid) throws IOException {

    Product product = new Product();
    // 1. 拼接url
    String pUrl = "https://item.jd.com/" + pid + ".html";

    // 2. 发起请求, 获取商品详情页的数据

    String html = HttpClientUtils.doGet(pUrl);

    // 3. 解析商品详情页
    // 3.1 获取dom对象
    Document document = Jsoup.parse(html);

    // 3.2 商品的标题
    Elements titleEl = document.select(".sku-name");

    product.setTitle(titleEl.text());

    // 3.3 商品的品牌
    Elements liEl = document.select("#parameter-brand>li");


    product.setBrand(liEl.attr("title"));

    // 3.4 商品的名称

    Elements liPnameEl = document.select("[class=parameter2 p-parameter-list] li:first-child");

    product.setPname(liPnameEl.attr("title"));

    // 3.5 封装pid 和 url

    product.setPid(pid);

    product.setUrl(pUrl);

    // 3.6 获取价格

    String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J_" + pid;

    // 发起请求获取数据
    String pirceJSON = HttpClientUtils.doGet(priceUrl);

    // 如何判断一个json字符串是数组还是对象:  最需要查看最外围的符号即可, 如果是[] 那么就是数组, 如果是{}那就是对象

    // gson和fastJson功能一样, gson是谷歌公司提供的一个json的转换工具

    Gson gson = new Gson();

    List<Map<String, String>> list = gson.fromJson(pirceJSON, List.class);

    Map<String, String> map = list.get(0);

    String price = map.get("p");


    product.setPrice(price);



    return product;

}


}
  • 分别打包 master与slave
  • 将master的jar包上传到master的服务器上
  • 将slave的程序分别上传到三台slave程序上
  • 将三台slave程序首先启动, 让其等待master即可
  • 启动master程序即可

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

android 的android httpClient详解

AndroidHttpClient结构: public final class AndroidHttpClient extends Object imple...

23750
来自专栏小灰灰

Java 动手写爬虫: 二、 深度爬取

第二篇 前面实现了一个最基础的爬取单网页的爬虫,这一篇则着手解决深度爬取的问题 简单来讲,就是爬了一个网页之后,继续爬这个网页中的链接 1. 需求背景 背景...

791100
来自专栏vue

.Net—反射

新建一个空白解决方案,添加一个控制台应用程序和一个名为Common的类库。在Common里面添加一个Person类和Student类,代码如下

24730
来自专栏博客园

Asp.Net Web API中使用Session,Cache和Application的几个方法

    在ASP.NET中,Web Api的控制器类派生于ApiController,该类与ASP.NET的Control类没有直接关系,因此不能像在Web M...

43910
来自专栏Create Sun

基础拾遗-----数据注解与验证

前言 其实对于这块知识点,一直觉得没有必要进行总结,只是新到的公司当时用到了 kendo for asp.net mvc ,里面有用到,自动初始化页面,而依据基...

337100
来自专栏跟着阿笨一起玩NET

关于DataGridView_DataError事件的问题

本文转载:http://blog.csdn.net/szstephenzhou/article/details/7834725

47610
来自专栏逸鹏说道

c# 温故而知新: 线程篇(一) 下

Abort 方法: 其实 Abort 方法并没有像字面上的那么简单,释放并终止调用线程,其实当一个线程调用 Abort方法时,会在调用此方法的线程上引发一个异常...

27060
来自专栏liulun

ASP.NET Core教程【三】实体字段属性、链接标签、并发数据异常、文件上传及读取

前文索引: ASP.NET Core教程【二】从保存数据看Razor Page的特有属性与服务端验证 ASP.NET Core教程【一】关于Razor Page...

32460
来自专栏GreenLeaves

Linq基础知识之延迟执行

Linq中的绝大多数查询运算符都有延迟执行的特性,查询并不是在查询创建的时候执行,而是在遍历的时候执行,也就是在enumerator的MoveNext()方法被...

266100
来自专栏技术博客

MVC项目开发中那些用到的知识点(Jquery ajax提交Json后台处理)

  jQuery提供的ajax方法能很方便的实现客户端与服务器的异步交互,在asp.net mvc 框架使用jQuery能很方便地异步获取提交数据,给用户提供更...

15420

扫码关注云+社区

领取腾讯云代金券