大数据互联网架构阶段 Java爬虫

Java爬虫

一 、 爬虫简介

  1. http://www.lete.com , 乐贷网其实就是爬虫的简单应用 ,发送一个商品连接 , 获取商品信息
  2. 目标
    1. 爬取京东所有商品的信息
    2. 封装在自己的Item实体类中
  3. 分析:
    1. 京东允许爬虫爬取数据么?
      1. 京东是允许爬虫的 , 没有反爬虫技术
  4. 爬虫产品:
    1. httpClient :但是httpClient抓取的是整个页面 , 整夜字符串的处理、解析比较繁琐 , 数据的定位非常不准确 。
    2. htmlUnit : 也获取整个页面 , 抓取页面也可以包含二次提交 , 数据定位也比较准确 , 但是爬取过程不稳定 , 在爬取过程中 需要断点续爬代码的编写 。
    3. jsoup: 是一款比较稳定 , 定位准确 , 包含二次提交的java爬虫技术 。
    4. python也可以做爬虫 , 使用beautifulSoup技术 ,底层原理与jsoup是一样的 。 只是语言不同。

jsoup

  1. 抓取整个页面
  2. 抓取整个网站(以京东为列 , 抓取从首页能获取所有的连接地址)
  3. 抓取页面中某一个定位的数据
  4. 抓取二次提交ajax(如 : price)
  5. 抓取其他的jsonp数据 (如: 商品描述)
  6. 以上五种问题 , 如果都能解决 ,那么使用jsoup爬取任何网站都是可行的 。

案例

  1. 整个页面

    1. 与httpclient无异

      /**
           * 爬取网页
           * @throws IOException 
           * */
          @Test
          public void testt_01() throws IOException {
              String url = "http://www.jd.com"; 
              Connection connect = Jsoup.connect(url);
              Response execute = connect.execute();
              System.out.println(execute.body());
          }
      
  2. 整个网站

    1. 抓取绝大部分的连接地址
    2. 观察网站的连接大部分都是使用的a标签 , 连接在href中
    3. 使用jsoup定位a标签 , 获取所有a标签 , 然后获取href的值

      /**
           * 爬取整个网站
           * @throws IOException 
           * */
          @Test
          public void test_02() throws IOException {
              String url = "http://www.jd.com";
              Document document = Jsoup.connect(url).get();
              //寻找a标签
              Elements elementsByTag = document.getElementsByTag("a");
              for(Element element :elementsByTag) {
                  String href = element.attr("href");
                  String val = element.val();
                  System.out.println("连接地址:"+href + "---"+val);
              }
          }
      
  3. 定位信息

        /**
             * 爬取一个网页中的信息
             * 定位具体标签中的数据
             * @throws IOException 
             * */
            @Test
            public void test_03() throws IOException {
                String url= "http://item.jd.com/4329035.html";
    
                //get请求 获取的是返回结构的document树
                //excute获取的是返回的所有数据
                Document doc = Jsoup.connect(url).get();
                //选择器与jQ中的选择器使用一致
                //为了 定位准确 , 使用父子选择器 , 确定唯一的定位
                Element select = doc.select("ul li .p-img a").get(0);
                System.out.println(select.attr("href"));
            }
    
  4. json二次提交获取信息

    1. 需要自己 寻找页面中发起 ajax的请求地址

      /**
           * 抓取二次提交
           * 商品价格是页面加载之后又通过ajax获取的
           * @throws IOException 
           * */
          @Test
          public void test_04() throws IOException {
              String url = "http://p.3.cn/prices/mgets?skuIds=J_5089253";
              Response response = Jsoup.connect(url).ignoreContentType(true).execute();
              String  json = response.body();
              System.out.println(json);
              ObjectMapper mp = new ObjectMapper();
              JsonNode jn = mp.readTree(json);
              //[{"op":"8388.00","m":"9999.00","id":"J_5089253","p":"8388.00"}]
              //直接获取到的是数组  ,需要获取到第一个元素
              String price = jn.get(0).get("p").asText();
              System.out.println(price);
          }
      
  5. jsonp数据

    /**
         * 获取jsonp请求数据
         * @throws IOException 
         * */
        @Test
        public void test_05() throws IOException {
            String url = "http://d.3.cn/desc/4329035";
            String jsonDesc = Jsoup.connect(url).ignoreContentType(true).execute().body();
            System.out.println(jsonDesc);
            String data = jsonDesc.substring(jsonDesc.indexOf("(")+1, jsonDesc.lastIndexOf(")"));
            System.out.println(data);
            ObjectMapper mp = new ObjectMapper();
            JsonNode jn = mp.readTree(data);
            String  desc = jn.get("date").asText();
            System.out.println(desc);
    
        }
    
  6. 爬取京东商品信息

    /**
     * 爬取京东商品的所有商品信息
     * @author outman 
     * 2018 - 1 - 31 - 17:48
     * 步骤: 
     * 1. 先获取所有的商品三级分类链接
     * 2. 访问商品分类链接后获取一个分类下所有商品的链接(可能存在分页的情况)
     * 3. 访问商品链接后获取商品信息 
     * 
     * 过程中要十分注意异常的处理
     * 在爬取过程中一旦出现异常 , 后续的过程也将受到影响 , 导致整个数据错乱
     * */
    public class JDCrawler {
        private static SqlSession session ; 
        static {
             //获取一个数据流
            InputStream in;
            try {
                in = Resources.getResourceAsStream("mybatis-config.xml");
                //创建一个工厂
                SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
                //创建一个会话
                session = factory.openSession(true);//true表示自动提交 , 默认为false , 需要手动提交
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        /**
         * 入口函数
         * @throws Exception 
         * */
        public static void main (String[] args) throws Exception {
            //测试
            //http://www.jd.com/allSort.aspx  商品 分类页面
    //      getItemCatUrls("http://www.jd.com/allSort.aspx");
            //list.jd.com/list.html?cat=12379,13302,13313 某一分类下的商品展示页面
    //      getItemsPageUrls("http://list.jd.com/list.html?cat=12379,13302,13313");
            //http://list.jd.com/list.html?cat=12379,13302,13313&page=2  商品展示页面
    //      getItemUrls("http://list.jd.com/list.html?cat=12379,13302,13313&page=2");
            //item.jd.com/12017077901.html  商品信息页面
    //      getItem("http://item.jd.com/12017077901.html");
            // 12017077901某一个商品的ID
    //      getPrice(new Long("12017077901"));
    
            //完整测试
            List itemCatUrls = getItemCatUrls("http://www.jd.com/allSort.aspx");
            for(String itemCaturl :itemCatUrls) {
                System.out.println("商品分类链接:"+itemCaturl);
                List itemsPageUrls = getItemsPageUrls(itemCaturl);
                for(String itemsPageUrl : itemsPageUrls) {
                    System.out.println("商品展示页面链接:"+itemsPageUrl);
                    List itemUrls = getItemUrls(itemsPageUrl);
                    for(String itemUrl : itemUrls) {
                        System.out.println("商品链接:"+itemUrls);
                        Item item = getItem(itemUrl);
                        saveItem(item);
                        System.out.println(item);
                    }
                }
            }
        }
        /**
         * 获取京东商品的所有分类链接
         * @throws Exception  
         * */
        public static List getItemCatUrls(String url) throws Exception{
            //记录数据数量
            Integer hrefPreNum  = 0  ;
            List itemCatUrls =  new ArrayList();
            //这里选择抛出异常 , 这里如果抛出异常 , 说明url有问题  , 或者网络有问题 , 后续的操作没有任何意义
            Document doc = Jsoup.connect(url).get();
            Elements eles = doc.select("dl dd a");
            for(Element ele : eles) {
                String href = ele.attr("href");
                hrefPreNum += 1;
                if(href.startsWith("//list.jd.com/")) {
                    itemCatUrls.add("http:"+href);
    //              System.out.println(href);
                }
            }
            System.out.println("获取到的总三级分类链接量:"+hrefPreNum);
            System.out.println("数据清洗后的数量:"+itemCatUrls.size());
    
            return itemCatUrls;
        }
        /**
         * 获取三级分类下所有商品页面的链接
         * 商品展示可能存在分页的情况
         * 所以在获取所有的商品链接之前需要先获取 所有的商品分类页
         * */
        public static List getItemsPageUrls(String url){
            List itemsPages = new ArrayList();
            //从商品展示页面获取分页信息
            String num;
            try {//抛出异常 , 如果 出现异常则继续执行 , 丢失一点信息是正常的
                num = Jsoup.connect(url).get().select("#J_topPage span i").get(0).text();
                Long numL = new  Long(num);
                for(int i = 1 ; i<=numL ; i++) {
                    String pageUrl = url+"&page="+i;
    //              System.out.println(pageUrl);
                    itemsPages.add(pageUrl);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return itemsPages;
        }
        /**
         * 获取每个商品分类页面的商品链接
         * */
        public static List getItemUrls(String url){
            List itemUrls = new ArrayList();
            try {
                Elements eles = Jsoup.connect(url).get().select(" li div .p-img a");
                for(Element ele : eles) {
                    String itemUrl = ele.attr("href");
                    itemUrls.add("http:"+itemUrl);
                }
            } catch (Exception e) {
                System.out.println("获取商品展示页面的商品链接出错:"+url);
            }
            return itemUrls;
        }
        /**
         * 访问商品链接 , 获取商品数据
         * */
        public static Item getItem (String url) {
            Item item = new Item();
            Long id = null;
            try {
                Document doc = Jsoup.connect(url).get();
                //获取id   //item.jd.com/12016709876.html
                id = new Long(url.substring(url.lastIndexOf("/")+1, url.indexOf(".html")));
                //获取title
                String title = doc.select("#name h1").get(0).text();
                //获取卖点  获取到的 值为"" 说明页面时是通过ajax方式请求  需要json格式的数据
    //          String sellPoint = doc.select("#p-ad").get(0).text();
                String sellPoint = getSellPoint(id);
                //获取价格  价格是通过ajax二次请求的
    //          Long price = new Long(doc.select(".dd .p-price .price").get(0).text());
                Long price = getPrice(id);
                //获取图片
    //          String img = doc.select("#spec-n1 img").attr("src");
                String img = getImg(url);
    //          System.out.println(img);
                //获取商品详情
    //          String desc = doc.select("J-detail-content").get(0).text();
                String desc = getDesc(id);
                //封装属性
                item.setId(id);
                item.setTitle(title);
                item.setSellPoint(sellPoint);
                item.setPrice(price);
                item.setImg(img);
                item.setDesc(desc);
                System.out.println(item);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.out.println("获取商品信息失败");
            }
            return item;
        }
        /**
         * 爬取卖点
         * 由于商品价格是页面加载完成之后 , 有通过ajax获取的 , 所以单独爬取json格式的数据
         * 通过页面分析 得到卖点的url
         * http://ad.3.cn/ads/mgets?skuids=AD_ +12017077901
         * */
        public static String getSellPoint(Long id) {
    
            String sellPoint = null;
            try {
                Response resp = Jsoup.connect("http://ad.3.cn/ads/mgets?skuids=AD_"+id).ignoreContentType(true).execute();
                ObjectMapper mapper = new ObjectMapper();
                 sellPoint = mapper.readTree(resp.body()).get(0).get("ad").asText();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.out.println("获取卖点失败");
            }
            return sellPoint;
        }
        /**
         * 爬取商品价格
         * 由于商品价格是页面加载完成之后 , 有通过ajax获取的 , 所以单独爬取
         * 通过页面分析 得到商品价格的链接 //p.3.cn/prices/get?skuid=id
         * */
        public static Long getPrice(Long id) {
            Long price  = null;
            try {
                Response resp = Jsoup.connect("http://p.3.cn/prices/get?skuid="+id).ignoreContentType(true).execute();
                ObjectMapper mapper = new ObjectMapper();
                JsonNode jsonNode = mapper.readTree(resp.body()).get(0);
                price = jsonNode.get("m").asLong();
    //          System.out.println(price);
            } catch (Exception e) {
                System.out.println("获取价格失败");
            }
            return price;
        }
        /**
         * 获取商品图片 
         * 通过分析页面 , 得到图片的请求地址
         * */
        public static String getImg(String url) {
            String img = "";
            Document doc;
            try {
                doc = Jsoup.connect(url).get();
                //获取页面大图的地址
                String bigsrc = doc.select("#spec-n1 img").attr("src");
    //          System.out.println("大图地址:"+bigsrc);
                //获取小图地址
                Elements smallsrcs = doc.select("#spec-list div ul li img");
                for(Element ele : smallsrcs) {
                    String src = ele.attr("src");
    //              System.out.println("小图地址:"+src);
                    //将小图地址替换成大图
                    String newSrc = src.replace("n5", "n1");
                    img+=newSrc+";";
    //              System.out.println(newSrc);
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.out.println("获取图片失败");
            }
            img = img.substring(0 , img.length()-1);
            return img;
        }
        /**
         * 爬取商品详情
         * 商品详情是页面加载完成之后 , 通过jsonp获取的 , 需要单独获取
         * http://dx.3.cn/desc/10316672107
         * */
        public static String getDesc(Long id) {
            String desc  = null;
            try {
                Response resp = Jsoup.connect("http://dx.3.cn/desc/"+id).ignoreContentType(true).execute();
                ObjectMapper mapper = new ObjectMapper();
                String body = resp.body();
                body = body.substring(body.indexOf("(")+1, body.lastIndexOf(")"));
                desc = mapper.readTree(body).get("content").asText();
    
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.out.println("获取不到"+id+"的商品描述");
            }
            return desc;
    
        }
    
        /**
         * 数据入库
         * */
        public static void saveItem(Item item) {
            session.insert("ItemMapper.saveItem" , item);
        }
    }
    

爬虫的注意事项

  1. 网络不稳定 , 最好使用完整的严谨 的逻辑(断点续爬)
  2. 爬虫代码量不大(逻辑种类不多) , 最重要 的是页面结构的分析
  3. 网站改版导致爬虫的代码更新 。
  4. 反爬虫技术
    1. 频繁修改样式关键字(最简单的反爬虫机制)
    2. nginx就可以反爬虫 (使用nginx黑名单)
      1. jsoup的连接 请求头和浏览器请求头不一样
        1. jsoup可以用代码模拟请求头—伪装请求头 参考: http://jilongliang.iteye.com/blog/2048459
    3. 查看访问频率 , 如果频率过高 , 则封ip一段时间

问题

  1. 数据是会每天更新或添加的 ,怎样在原有的基础上爬取最新的数据

你可能感兴趣的:(WEB,大数据)