WebMagic爬取《哪吒》豆瓣影评

1.前言

前言: 在某大佬的公总号上看到使用Python爬取《哪吒》影评的项目小样,心痒痒,我也想用Java来搞一搞,遂动手。
项目基础: 1. JavaClient,Jsoup了解下。 2. WebMagic框架熟悉:WebMagic官网 ,挺简单的。3,老三件套ssm,mybatis用的是mybatis-plus,mysql
项目地址: 戳这里

2.项目

目标: 爬取哪吒豆瓣影评的信息→爬取信息:用户姓名 星星数量 评语 评论时间 赞同数 反对数 正文链接。一共爬取400条信息用于后期分析用途。
目标url: 哪吒豆瓣影评地址

我们看一下我们需要爬取的页面情况:巧了,我们需要爬取的信息都在一个div里,也就一个页面就拥有我们需要的20条信息,本来还想爬取用户信息,头像,地址等的,萌新不知道为啥爬虫死活进不去用户主页,就放弃了。知道的希望告知一下。我们通过使用xpath,css选择方法选择对应标签,获取文本值/属性值就ok了。
在这里插入图片描述
项目组成:
下面来看一下项目具体情况吧,config包,controller,log4j的配置不用看。
在这里插入图片描述
POM文件: 使用的springboot版本为最新的2.1.7

	<dependencies>	
		<!--WebMagic-->
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--WebMagic扩展-->
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.3</version>
        </dependency>
        <!--工具包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
   </dependencies>

yml配置信息:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost/spider?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: xxx
    password: xxx
//关闭驼峰转下划线
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false

用户信息:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("film_review")
public class UserInfo {
    //用户姓名  星星数量 评语 评论时间 赞同数 反对数 正文链接
    private int id;
    private String userName;
    private int starNumber;
    private String comment;
    private Date commentTime;
    private int agreeStar;
    private int againstStar;
    private String textLink;
}

Mapper:

@Repository
public interface UserMapper extends BaseMapper<UserInfo> {
}

Service:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 保存UserInfo
     * @param userInfo
     */
    public void save(UserInfo userInfo){
        userMapper.insert(userInfo);
    }

    /**
     * 根据用户名和点赞数量来判断数据是否已存在
     * 有数据,返回true,反之返回false
     * @param userInfo
     * @return
     */
    @Transactional
    public boolean judgeHaveUserInfo(UserInfo userInfo){
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userName",userInfo.getUserName())
                .eq("agreeStar",userInfo.getAgreeStar());
        List<UserInfo> result = userMapper.selectList(queryWrapper);
        if(result.size()==0){
            return false;
        }else {
            return true;

        }
    }
}

主要程序:

@Component
public class MyProcessor implements PageProcessor {
    @Autowired
    private MyPipeline myPipeline;

    private static int time;

    //设置请求配置
    private Site site = Site.me()
            .setRetrySleepTime(2000)
            .setRetryTimes(3)
            .setSleepTime(2000);

    //爬虫主要的处理流程
    @Override
    public void process(Page page) {
        Html html = page.getHtml();
        //爬取信息:用户姓名  星星数量 评语 评论时间 赞同数 反对数 正文链接
        //爬取评论时间
        List<String> commentTime = html.css(".main-hd .main-meta", "text").all();
        //爬取赞同数量
        List<String> agreeStar = html.xpath("//*div[@class='main-bd']/div[3]/a[1]/span/text()").all();
        //爬取反对数量
        List<String> againstStar = html.xpath("//*div[@class='main-bd']/div[3]/a[2]/span/text()").all();
        //爬取正文链接
        List<String> textLink = html.xpath("//*div[@class='main review-item']/div/h2").links().all();
        //爬取用户姓名
        List<String> name = html.css(".main-hd .name", "text").all();
        //爬取星星数
        List<String> startNumber = html.xpath("//div[@class='main review-item']/header/span[1]/@title").all();
        //爬取评论小标题
        List<String> comment = html.css(".main-bd h2 a", "text").all();
        List<UserInfo> userList = new ArrayList<>();
        for (int i = 0; i < name.size(); i++) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName(name.get(i));
            //评价星星数str→int
            userInfo.setStarNumber(starNumberStrToInt(startNumber.get(i)));
            //String→date
            userInfo.setCommentTime(strToDate(commentTime.get(i)));
            //String→int
            userInfo.setAgreeStar(starStrToInt(agreeStar.get(i)));
            //string→int
            userInfo.setAgainstStar(starStrToInt(againstStar.get(i)));
            userInfo.setTextLink(textLink.get(i));
            userInfo.setComment(comment.get(i));
            userList.add(userInfo);
        }
        //输出
        page.putField("userInfo", userList);
        //爬取次数减一,并抓取下一页的超链接
        if (--time == 0) return;
        List<String> nextPage = html.css(".paginator span.next").links().all();
        //添加请求
        page.addTargetRequests(nextPage);
    }

    @Override
    public Site getSite() {
        return site;
    }

    /**
     * 空字符串处理,爬取的数据可能为""
     * @param str
     * @return
     */
    private int starStrToInt(String str) {
        if (str != null && !"".equals(str.trim())) {
            //则字符串不为空或空格
            return Integer.parseInt(str.trim());
        }else {
            return 0;
        }
    }

    /**
     * 字符串日期处理
     * @param str
     * @return
     */
    private Date strToDate(String str) {
        //需要转换的字符串必须和解析的格式相对应
        SimpleDateFormat smp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = null;
        try {
            date = smp.parse(str);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    /**
     * 文字评价转数量
     * @param startNumber
     * @return
     */
    private int starNumberStrToInt(String startNumber) {
        switch (startNumber) {
            case "很差":
                return 1;
            case "较差":
                return 2;
            case "还行":
                return 3;
            case "推荐":
                return 4;
            case "力荐":
                return 5;
            default:
                return 0;
        }
    }

    /**
     * 爬取次数的设定
     * @param time
     */
    private static void setTime(int time) {
        MyProcessor.time = time;
    }

    /**
     * 开启爬虫的入口
     */
    @Scheduled(initialDelay = 1000, fixedDelay = 1000 * 100000)
    public void run() {
        setTime(20);
        System.out.println("爬取哪吒豆瓣影评开始啦--------------");
        long start = System.currentTimeMillis() / 1000;
        Spider.create(new MyProcessor())  //创建爬虫
                .addUrl("https://movie.douban.com/subject/26794435/reviews") // 添加目标页面
                .thread(5)  //并发数量
                .addPipeline(myPipeline) //添加自定义目标
                .run();  //运行
        long end = System.currentTimeMillis() / 1000;
        System.out.println("该次爬取共耗费时间:" + (end - start) + "秒");
    }
}

自定义输出:

/**
 * 实现Pineline,重写process方法,修改数据的输出方向
 */
@Component
public class MyPipeline implements Pipeline {

    @Autowired
    private UserService userService ;
    
    @Override
    public void process(ResultItems resultItems, Task task) {
        List<UserInfo> userInfo = (List<UserInfo>)resultItems.get("userInfo");
        if (userInfo != null) {
            //进行判断数据库是否已经存在该数据
            for (int i = 0; i < userInfo.size(); i++) {
                if (!this.userService.judgeHaveUserInfo(userInfo.get(i))){
                    this.userService.save(userInfo.get(i));
                }
            }
        }
    }
}

这里说一下如何运行爬虫,是在启动里面运行呢,还是搞一个监听器运行呢,还是定时运行。我这里采用的是定时运行,约等于只运行一次。
运行: 结果是这样的,下一步准备搞个前端,再搞一个可视化分析。
在这里插入图片描述

3.项目问题总结

  • 必须打好日志,要不然都不知道哪里报错。
  • date与string的装换,str与int的转换,Integer.parse(XXX)的参数接受字符串不能为null或者""。
        if (str != null && !"".equals(str.trim())) {
            //则字符串不为空或空格
     		//trim()函数的作用:取出字符串前后的一段连续空格
        }else {

        }
  • xpath与jsoup选择语法,WebMagic官网搞定。
  • 日志冲突解决,WebMagic包自带log4j日志包与springboot-web包带的日志包冲突,去掉即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值