第一次爬虫记录,爬取商品基础数据以及图片

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流,qq911283415。 https://blog.csdn.net/HaixWang/article/details/81008663

需求

爬取商品价格、销量、评论、收藏量、款式等基本数据以及详情图。

遇见的主要问题以及解决方式

对于一个从来没写过爬虫的人来说很多地方都是很困惑的。而且公司要求两三天就得出结果并用于生产,再加上自己轻微的代码洁癖,综合起来还是有些压力的。
所以也没时间去学习一些爬虫框架或者道友们的一些实现方案,回头来看,都是些常规操作。

写完之后,发觉这并不是一篇好文章,朋友们在这篇文章中可能获得不了太多有价值的信息
这篇文章其实不错

第一个问题

最开始抽取所有的商品URL,要爬取的网站的数据是Ajax动态加载的。所以直接代码获取链接数据,只会返回最初的静态页面。里面几乎不会有什么可用的信息。

你可以下载一个”toggle JavaScript”的插件,启动后点击该插件,可以看到哪些显式是通过JS异步加载过来的(是点击该插件,而不是仅仅启动就可以)

比如,未使用该插件时:

这里写图片描述

点击该插件过后,会发现很多数据没有加载出来,这里加载出来的都是最初的静态页面。往往都是“item.html“

这里写图片描述

要解决这个问题,主要得找到是哪个js脚本返回的数据,找到后用jousp解析html。我们需要一个个的去查看,看是哪个地方返回了我们需要的数据

这里写图片描述

数据往往都是以JSON的形式返回的,找到该入口后,接下来就是解决翻页。
我这里是直接在URL尾部添加&pageNo=,是可行的。

            Document doc = null;
            try {
                // 解决UnsupportedMimeTypeException:ignoreContentType(true)
                doc = Jsoup.connect(entry.getValue()).headers(SpiderHeader.headers).ignoreContentType(true).timeout(30000).get();
            } catch (IOException e) {
                e.printStackTrace();
            }

后来发现请求淘宝移动端API可以基本满足我的需求,故没有继续慢慢的踩Ajax异步加载的坑
API中可能会有近十个参数,但是很多参数是可以去掉的。

还一种解决方案是采用第三方工具,模拟人的浏览器点击行为来使得数据加载。比如:Selenium、PhantomJs,但是这种方式感觉更繁琐,大家说效率也更低,甚至有可能在浏览器中开大量网页。没试过这种方式。

获取所有的商品URL后,解析JSON的问题

这里是自己挖的一个小坑,用jousp获取网页数据解析html是没有问题的,但是用jousp获取网页数据再用Jackson解析Json是会出现问题的。
因为这个Jsoup在返回的数据不符合HTML规范时,会构造<html><body>等标签,导致JackSon解析出错。
改用java.net.URL获取数据:

String jsonText;
StringBuilder sb = null;
InputStream is = new URL(entry.getValue()).openStream();
System.out.println("接口地址:" + entry.getValue());
try {
    BufferedReader brd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
    sb = new StringBuilder();
    int cp;
    while ((cp = brd.read()) != -1) {
        sb.append((char) cp);
    }
} catch (IOException e) {
    e.printStackTrace();
}
jsonText = sb.toString();

如果数据量较大,这里的StringBuilder的read()方法的效率可能会比较低,可改用read(buff)、readLine()等方法

Jsoup解析HTML较常用的方法:
- getElementsByTag定位到所有指定的标签
- text获得该Element下所有文字,空格隔开
- attr定位当前标签下的属性

Elements links = doc.getElementsByTag("dl");
        System.out.println("获得dl标签个数:" + links.size());
        for (Element e : links) {
            // 去除ID中非法字符
            String regex = "[`~!@#$%^&*()+=|{}':\";,\\\\.<>/?!¥…()—【】‘;:”“’。,、?]";
            String id = e.attr("data-id").replaceAll(regex, "");
            System.out.println("新数据" + e.text());
            hashMap.put(id, e.text());
        }

入库的问题

这个已经不属于爬虫范畴了,为了“省事儿“以及方便后期的扩展,便使用Mybatis操作数据库,可是一个没有错误堆栈信息的NullPointerException异常搞得我Debug半天都没找准问题根源,时间紧迫,还是手撸了一遍JDBC。

访问速度问题

访问太频繁会被反爬,设置同一sleep时间,也会被反爬。
设置一随机sleep时间即可。
比如:

// random sleep
    Random rand = new Random();
    Thread.sleep(rand.nextInt(3000) + 500);

下载图片

    DataInputStream dis = new DataInputStream(url.openStream());
    String newImageName = "/Users/wanghai/IdeaProjects/imgs/" + entry.getKey() + "_" + nameFlag + ".jpg";
    // new imgFile
    FileOutputStream fos = new FileOutputStream(new File(newImageName));
    byte[] buffer = new byte[1024];
    int length;
    // write img data
    while ((length = dis.read(buffer)) > 0) {
        fos.write(buffer, 0, length);
    }
    dis.close();
    fos.close();

这里写图片描述

其他问题

剩下就是一些常规操作和打日志了。

其实过程中还有犯不少错,但是没有及时的记录,现在也想不起具体的问题了。大都是一些判断无效链接、判断返回的JSON数据哪些无效,以及一些边界问题。
所以这就体现出单元测试的重要性了,个人感觉,对于需要进行很多的过滤、判断、匹配操作的时候,除了在理清思路、想出一些基本的边界问题之后再码代码之外,在每一个阶段性功能实现后就应该进行跟进测试,不然当写完所有逻辑之后,出现运行不报error但是结果却不是你想要的结果的情况时,排查起来还是比较麻烦的。

剩下的思路,就是获得所有需要请求接口的链接等信息,不使用多线程的话,封装到HashMap就行。请求接口后将需要数据解析出来封装到对象中,将对象置于ArrayList< ? extends SomeClass>,遍历arrayList,拿出每个对象,拼接添加sql语句,完成后,batch批量插入。以避免每一个对象插入一次,大量的数据库建立链接以及销毁链接的开销。

写在末尾的感慨:
好久没有做大数据相关的事情了,久了不碰,真的容易忘,时间时间时间啊。


2018-07-18增:

接口来的图片还是太少,公司要求要详情页所有图片,于是:
这里写图片描述

这就是硬爬图片了,在详情页中,查看任意以图片的连接地址,然后在F12中搜索关键词
这里写图片描述

双击搜索结果,很有可能就会得到你想要的数据返回链接,在该链接中,有详情页所有图片。

但是该链接,貌似无法拼凑,我尝试删减任意一参数,或者替换itemId,都是拒绝访问。但是考虑到我这商品数量并不多,两百来个,就化了2小时自己录入了链接。

接下来就是用Jsoup解析html(Jsoup会对返回的数据添加等表头,【如果没有的话】),然后使用select,对class、属性、标签进行选择,筛选自己需要的数据。

// 解析html,获得商品url
                String regex = "^https://img.alicdn.com/imgextra/i.*";
                Pattern p = Pattern.compile(regex);
                StringBuilder stringBuilder = new StringBuilder();
                Elements elements = doc.select("div[data-title=\"模特效果图\"]");
                System.out.println("详情图elements size 期望是1,实际上是:" + elements.size());
                for (Element e : elements) {
                    Elements elements2 = e.getElementsByTag("img");
                    for (Element e2 : elements2) {
                        String contents = e2.attr("src");
                        Matcher m = p.matcher(contents);
                        while (m.find()) {
                            stringBuilder.append(contents.substring(m.start(), m.end()));
                            stringBuilder.append(",");
                        }
                    }
                }

猜你喜欢

转载自blog.csdn.net/HaixWang/article/details/81008663