Java 爬虫之爬取小米网收货地址

版权声明:转载请注明出处 https://blog.csdn.net/YoshinoNanjo/article/details/82905530

       其实这个代码去年我就在项目里写好了,只是去年我并没有玩博客……现在想想挺有趣的,记录下来。当然了,我做了一些简化处理,比如不建立表,不保存入库,由各位读者根据实际情况自己去处理,反正在我这如果要加上保存入库的代码就是一两行的事情,前提是各种类要封装好。

       我们来看一下小米网的所有收货地址是什么样的:传送门

       看到了吧,除了开头那77个字符串是没用的以外,剩下的都是标准json字符串,那么我们就解析这个网页这个json就可以了。这里我先创建一个JavaBean:Address类,采用树形结构,这种树形结构比较新颖,根据parent、lft、rgt来构成,不过我这里因为不保存入库,这里不对lft和rgt两个字段设置值,读者只需关心id、name、parent就行。小米网的每一个收货地址就是一个Address类。

public class Address {

    private String id;

    private Address parent;

    private Integer lft;

    private Integer rgt;

    private String name;

    private Integer priority;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Address getParent() {
        return parent;
    }

    public void setParent(Address parent) {
        this.parent = parent;
    }

    public Integer getLft() {
        return lft;
    }

    public void setLft(Integer lft) {
        this.lft = lft;
    }

    public Integer getRgt() {
        return rgt;
    }

    public void setRgt(Integer rgt) {
        this.rgt = rgt;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getPriority() {
        return priority;
    }

    public void setPriority(Integer priority) {
        this.priority = priority;
    }
}

       接下来开始爬取,创建一个入口类,给定两个静态成员变量:

/**
 * 导入 Jackson 库,用于 Java 对象和 json 字符串的互相转换
 */
static ObjectMapper objectMapper = new ObjectMapper();

static List<Address> addressList = new ArrayList<Address>();

       main 方法,这里的链接就是我上文给的传送门:

public static void main(String[] args) {
    try {
        Document doc = Jsoup.connect("https://s1.mi.com/open/common/js/address_all_new.js").ignoreContentType(true).get();
        Elements body = doc.select("body");
        // outerHtml:将把 <body> 标签一并输出
        // html:只输出 <body> 标签里面的内容
        // 裁掉前面无用的 77 个字符串,得到所有地址的 json 字符串
        String html = body.html().substring(77);
        // 转换为集合
        List list = objectMapper.readValue(html, List.class);

        save(null, list);

        System.err.println(addressList);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

       save 方法,用到了递归的思想:

private static void save(Address parent, List<Map> list) {
    for(Map map : list) {
        Address address = new Address();

        String id = map.get("id").toString();
        String name = (String) map.get("name");

        address.setId(UUID.randomUUID().toString());
        address.setName(name);

        if(parent != null) {
            address.setParent(parent);
        }
        addressList.add(address);

        List<Map> childList = (List<Map>) map.get("child");
        if(childList != null) {
            save(address, childList);
        } else {
            try {
                String url = "https://order.mi.com/api/getAddressRegion.php?jsonpcallback=jQuery111305980845644014263_1505877765419&parent="+ id +"&_=1505877765434";
                Document doc = Jsoup.connect(url)
                                    .ignoreContentType(true)
                                    .referrer("https://item.mi.com/product/10000057.html") // 必须设置请求来源,只要是小米网的地址就行
                                    .get();
                // 以下变量见下文讲解
                Elements body = doc.select("body");
                String html = body.html().substring(42);
                html = html.substring(0, html.length() - 2);
                Map _map = objectMapper.readValue(html, Map.class);
                if (_map != null && !_map.isEmpty()) {
                    Map dataMap = (Map) _map.get("data");
                    if (dataMap != null && !dataMap.isEmpty()) {
                        Map<String, Map> regionsMap = (Map) dataMap.get("regions");
                        for(Map.Entry<String,Map> entry : regionsMap.entrySet()) {
                            String regionId = entry.getKey();
                            Map regionValue = entry.getValue();

                            Integer _regionId = (Integer) regionValue.get("region_id");
                            String _regionName = (String) regionValue.get("region_name");
                            Integer _sort = (Integer) regionValue.get("sort");

                            Address street = new Address();
                            street.setId(UUID.randomUUID().toString());
                            street.setParent(address);
                            street.setName(_regionName);
                            street.setPriority(_sort);

                            addressList.add(street);
                        }
                    }
                }

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

       body变量:

body 变量值

       利用 html() 转换成文本再裁掉无用的42个字符串:

裁掉42个无用字符串后的值

       只要再裁掉最后2个字符串就能构成一个标准json了,剩下不用我说也都知道了吧。

       还有一点就是为什么必须设置请求来源,我们先看一下不设置就去请求小米的那个链接是什么效果:

请求来源不正确

       它会直接告诉你请求来源不正确,简单来说就是小米认为你是通过非法途径来到我这个网址的,滚;那什么是合法的呢?就是我从小米的域名跳到这个网址它会认为是合法的,毕竟是自家人。那么我们就设置请求来源为小米的一个域名,什么都行,只要是小米的域名就行。

       非法的请求来源,即在请求头中没有 Referer 属性,或者非小米网的网址

非法的请求来源

       合法的请求来源,你再去Preview选项卡就能看到获取的数据,就是我上文贴的那些变量图片:

合法的请求来源

       可能有些人有疑问,我那两个链接是怎么知道的,很简单啊,你随便找一个小米的商品,到购买页,随便点几下收货地址,注意看Network,从中找就行了:

小米网收货地址页面

       最后我们看一下爬取成果,一共有48000多条数据啊,当初我存到数据库都存了好一会儿……

爬取结果

猜你喜欢

转载自blog.csdn.net/YoshinoNanjo/article/details/82905530