Java实现爬虫的文件IO的一些总结

来源:互联网 发布:天刀捏脸数据在哪儿看 编辑:程序博客网 时间:2024/06/07 15:17

简介

最近在写论文时需要用到爬虫。总的思路是:先获取一级页面的所有产品列表;接着获取二级页面的该产品的指定页数的评论。

举个例子:

第一步:在搜索框中输入:足球鞋,出现下图,利用爬虫获取所有产品的基本信息,数据存放在数据库中一个表page_first


第二步:接着进去第一个产品的界面,爬取11月12号之前一个月的总计10页评论(大家不要关注为什么要选择11月12号,论文需要,与该博客无关),数据存放在数据库的表page_second中。以此类推,获取一级页面中所有产品的11月12号之前的10页评论。

下面是数据库的设计



目的:

目的主要有三个,一是给哪些和我一样刚开始爬虫并集中处理文件和数据的小伙伴一些处理思路;二是将处理过程中遇到的两个问题着重介绍下;三是希望有对我上面两点有什么建议或者好的想法的人可以给我一些改进的意见。

重点

page_second的外键是page_first的主键,我的目标是在进行到第二步的插入数据到数据库时,能够同时把productId也插入(productId即为那个外键)。由于这里的爬虫其实比较简单(整体的代码我会在最后上传),同时由于本人是第一次接触java的IO,真正难住我的是执行第二步时的程序(涉及到很多java的基础知识).。所以本篇博客的目的是第二步的设计。

思路:1.将page_first中的所有productId(主键)放到文件productId.txt文件中,如下图


2.将每个产品的所有评论的url放到pachong文件夹中,但是有个问题,就是有的url第一次执行不了,但是第二次可以执行。这样的后果是程序会就此中断。如果再次启动程序的话会导致已经执行过的url再次被执行,这样数据库中就有了重复的数据。我的做法是:将所有没有执行过的url(包括执行错误的url)放入一个list之中。每当一个二级循环结束之后,就用这个list重写n.txt。然后中断之后重新启动程序,这样就保证了所有url所代表的数据全部插入数据库,并且没有重复的。如下图:


废话不多说,直接上代码(主函数和各种自己写的util文件):

主函数:

public class TmallMain {    static long productId = 0;    static String entity = null;    static HttpClient client = HttpClients.createDefault();    //用于存放所有产品的文件    static List<File>  files = new ArrayList<File>();    //用于存放某个产品的所有url    static List<String> urls = new CopyOnWriteArrayList<String>();    //用于存放某个产品的某个url的所有pojo对象    static List<TmallPojo2> reviews = new ArrayList<TmallPojo2>();    //用于存放所有的productId    static List<Long> productIds = new ArrayList<Long>();    //为了避免IndexOutOfBoundsException: Index: 3, Size: 3,这一点我解决了一个小时,等会重点讲解下。    static List<String> urlsNew = new ArrayList<String>();    public static void main(String[] args) throws Exception{        //1.拿到所有产品url的文件列表        files = FileUtil.getFiles("D://pachong");        //2.获取所有的productId的列表        productIds = FileUtil.getProductIds("D://productId.txt"); //这里之前也出现了IndexOutOfBoundsException,这是一种解决手段        int count = files.size();        //3.对文件列表循环处理        for(int i=0; i<count; i++) {            productId = productIds.get(i);            urls = FileUtil.getListFromFile(files.get(i));            int count2 = urls.size();
   //如果n.txt中的每个url都解析成功了,跳出本次循环,执行n+1.txt            if(urls.size() == 0) {                continue;            }            //3.1对每个文件(产品)的所有url做处理            //如果n.txt中有url没有解析成功,当url解析成功时,删除该url;否则不删除该url            else {                for(int j=0; j<count2; j++) {                    entity = URLFecter.urlParser(client, urls.get(j));
//之所以是"rgv587_flag"是因为我发现每次没有成功执行的url的entity都有这样的字符串,所以作为区分的标志                    if(entity.contains("rgv587_flag")) {                        urlsNew.add(urls.get(j));                        continue;                    } else {                        reviews = TmallParser.getReviewData(entity,productId);                    }                    System.out.println("reviews:   " + reviews);
//数据插入数据库                MySQLController.executeInsert2(reviews);                }                System.out.println("是否需要重写url的文件:" + urlsNew.size());                //当某个产品的url全部解析完之后,在磁盘上删除该文件;否则用剩下的url重写该文件                if(urlsNew.size() == 0) {                    FileUtil.rewriteUrlFile(urlsNew, files.get(i));                    urlsNew.clear();                } else {                    FileUtil.rewriteUrlFile(urlsNew, files.get(i));                    urlsNew.clear();                }            }        }    }}
涉及到的各种util包:

FileUtil.java

public class FileUtil {    //从D://pachong文件夹下获取所有的文件形成列表    public static List<File> getFiles(String path) {        List<File> files = new ArrayList<File>();        File file = new File(path);        File[] fileArray = file.listFiles();        for (File f : fileArray) {            files.add(f);        }        return files;    }    //从D://productId.txt下获取所有的productId形成列表    public static List<Long> getProductIds(String path) throws Exception{        List<Long> productIds = new ArrayList<Long>();        FileReader fr = new FileReader(new File(path));        BufferedReader br = new BufferedReader(fr);        String line = null;        while((line = br.readLine()) != null) {            productIds.add(Long.parseLong(line));        }        br.close();        return productIds;    }    //对每个文件做循环读取,获得String的列表    public static List<String> getListFromFile(File file) throws Exception {        List<String> list = new ArrayList<String>();        FileReader fr = new FileReader(file);        BufferedReader br = new BufferedReader(fr);        String line = null;        while((line = br.readLine()) != null) {            list.add(line);        }        br.close();        return list;    }    //对没有全部url解析完全的文件进行重写    public static void rewriteUrlFile(List<String> list, File file) throws Exception {        String newUrl = "";        String line = null;        for(String url : list) {            newUrl = newUrl + url + "\n";        }        StringReader sr = new StringReader(newUrl);        BufferedReader br = new BufferedReader(sr);        FileWriter fw = new FileWriter(file);        while((line = br.readLine()) != null) {            fw.write(line);            fw.write("\n");        }        br.close();        fw.close();    }}
StringUtil.java

public class StringUtil {    //1.将entity转为json数据    public static String getJson(String entity) {        int startIndex = entity.indexOf("{");        int endIndex = entity.lastIndexOf(")");        String json = entity.substring(startIndex, endIndex);        return json;    }    //2.将String的日期转为date的日期    public static Date getDate(String stringDate) {        String s = "2017-11-04 02:20:25";        int year = Integer.parseInt(s.substring(0,4));        int month = Integer.parseInt(s.substring(5,7));        int day = Integer.parseInt(s.substring(8,10));;        int hour = Integer.parseInt(s.substring(11,13));        int min = Integer.parseInt(s.substring(14,16));        int sec = Integer.parseInt(s.substring(17,19));        Date date = new Date(year-1900,month-1, day, hour, min, sec);        return date;    }}

两个困惑:

两个困惑,确切的说是一个困惑,一个bug。困惑就是对于IO和基本数据类型操作的一些困惑,bug就是我上面说的IndexOutOfBoundException。

先看IO的困惑:先总结下这一次使用IO之后的一些对于IO和java学习的体会吧。

IO,我觉得可以分为两大块,一个是File,一个是输入输出(就是真正的IO)。

第一块的话:

1.大家可以看下File类的api文档,方常用的法可以分为创建File的构造方法(注意,这里创建的只是在内存中的File对象,在硬盘上还不存在,没有被持久化)、内存File的持久化方法(mkdir和mkdirs、createNewFile)、判断持久化之后的File是否存在的exists方法(注意判断的是硬盘上的File是否存在,还没有持久化的内存File使用此方法返回的是false)、获取File信息的一干方法(listFile、getName、getParent等);

2.基本IO的输入(reader、inputStream)的构造函数的入参我目前遇到的有File、Object、String,所以这个File类作为IO的入参之一还是要理解的足够透彻的;

第二块的话:

1.IO按照字节和字符来说的可以分为字节输入输入流、字符输入输出流。按照我目前经常用到的、以及个人自己的理解可以分为:FileInputStream(OutputStream)、FileReader(FileWriter)。这两者可以归为一类,因为用法太相似了。一个处理字节一个处理字节,而且每个在读取前指定每次读取的大小;还有一类是StringReader(StringWriter),之所以把这分为一类是因为虽然字节、字符、字符串之间是递增的关系。但是由于输入对象构造方法的三大入参之一是String,所以我想把它单独分为一类;第三类就是缓冲类,缓冲类非常好用,因为可以按行读取文件,非常方便。而且由于它是先将数据还存在内存中到了一定程度之后在进行读取和写入,所以速度很快;第四类就是转换流,虽然还没有用到,但是觉得非常有用;

2.关于以上前三类,对于第一类和第二类,在没有使用到缓冲流时目前我觉得需要注意的是(即使会用,但是会用和理解还是不一样的吧)理解每次读取和写入前需要指定读取和写入块的大小;第三类我觉得需要理解的缓冲流的原理、每种缓冲流与字节字符流的对应;

3.明白输入对象在构造时的三个入参,常用的是File和String。

最后一块是Java基础类型的一些理解

由于抓取的数据全部为json字符串,也就是字符串类型。但是有的日期类数据和数值型数据类型,所以要做一些数据类型之间转换的处理。比如:String类型转为Date类需要用到String类的一些方法(最多的就是subString方法、indexOf方法和lastIndex方法)获取年、月、日等字符串,然后将这些字符串转为int,再将int值作为入参传入Date的构造方法。下面介绍下七种基本数据类型(八种基本数据类型去除了byte)、String之间的转换:

七种数据类型之间的转换、向上转换直接进行就可以(比如int转为long),向下转型(比如long转int)使用强制转换符即可;其中数据类型转为String可以使用String的valueOf方法、String转为基本数据类型可以使用各数据类型的parse方法,比如Integer的parseInt方法。如果需要更复杂的类之间的转换需要自己写一些util包;

第二块的话是一个循环体经常出现的IndexOutOfBoundException,大家看下我之前的代码

        for(int i=0; i<files.size(); i++) {            productId = productIdsNew.get(i);            urls = FileUtil.getListFromFile(files.get(i));            int count2 = urls.size();
            if(urls.size() == 0) {                continue;            }            else {
                for(int j=0; j<count2; j++) {                    entity = URLFecter.urlParser(client, urls.get(j));                    if(entity.contains("rgv587_flag")) {                        urlsNew.add(urls.get(j));                        continue;                    } else {                        reviews = TmallParser.getReviewData(entity,productId);                    }                    System.out.println("reviews:   " + reviews);                MySQLController.executeInsert2(reviews);                }                System.out.println("是否需要重写url的文件:" + urlsNew.size());                if(urlsNew.size() == 0) {
    //问题2                    productIds.remove(i);                    FileUtil.rewriteUrlFile(urlsNew, files.get(i));                    urlsNew.clear();                } else {                    FileUtil.rewriteUrlFile(urlsNew, files.get(i));                    urlsNew.clear();                }            }        }
        FileUtil.rewritePIdFile(productIds, pIdFile);    }}
代码中我标注出来了这个问题的位置,先来分析下为什么会报这样的一个错误

假设我的productIds={1,2,3,4,5},同时我有5个txt文件,即1.txt、2.txt、3.txt、4.txt、5.txt。

i=0时,此时对应的productId为1,如果所有的url没有全部解析成功,直接看二级循环:urlsNew.size()就为0,那么执行productIds.remove(0)方法,那么productIds就变成了{2,3,4,5};

i=1时,此时的productId为3(按照代码的初衷应该为2,之所以为3,是因为productIds改变了,原来的索引为1的元素由2变成了3),如果所有的url还是没有解析成功,直接看二级循环:urlsNew.size()就为0,那么执行productIds.remove(1)方法,那么productIds就变成了{2,4,5};

i=2时,此时的productId为5,如果所有的url还是没有解析成功,直接看二级循环:urlsNew.size()就为0,那么执行productIds.remove(2)方法,那么productIds就变成了{2,4};

i=3时,此时就会报错,因为请求的index为3,但是productIds的最大索引为1,所以出现了IndexOutOfBoundException的异常。

错误的原因是:循环体内要循环使用productIds这个列表,所以这个列表是不能变的。但是我这个程序里productIds是在不断缩小的,而j的大小又是不变的(原先设计的时候productids的尺寸就是j)。

解决办法:不在循环体内remove(这个我是没有想到好的解决方法),第二就是我不在重写productIds.txt,所以就不需要了。带式还是可以达到我预期的功能。

需要完整代码的可以在留言里面告诉我一下,新手第一次写CSDN,肯定有很多做的不好的地方,希望大家多多包涵!!!


原创粉丝点击