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包帶的日誌包衝突,去掉即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章