1. 程式人生 > >基於webmagic的爬蟲小應用--爬取知乎使用者資訊

基於webmagic的爬蟲小應用--爬取知乎使用者資訊

最近跟舍友@小瘋一起研究爬蟲
他寫了個小應用-CSDN部落格爬蟲 有興趣的朋友可以點進去看看哦~
一起學習。
一起進步。
想要原始碼的朋友點選這裡下載哦~

聽到“爬蟲”,是不是第一時間想到python/php ? 多少想玩爬蟲的java學習者就因為語言不通而止步。Java是真的不能做爬蟲嗎?
當然不是。
只不過python的3行程式碼能解決的問題,而Java要30行。

這裡推薦大家一個大牛做的java爬蟲框架 【WebMagic】
文件簡單易懂!java爬蟲開發的福利啊!
一起來動手做一個小應用吧!

爬蟲小應用–知乎使用者資訊

爬蟲思想有3步
1. 抽取目標連結


2. 抽取需要的資訊
3. 處理資料

接下來檢視html結構,確定待爬取的目標連結。(這裡我的目標連結是【前10個使用者的詳細資訊頁面的url】

這裡寫圖片描述

二、抽取需要的資訊(webmagic提供了3種方式,xpath,css選擇,正則表示式。具體可以檢視下[WebMagic文件](http://webmagic.io/docs/zh/))
這裡寫圖片描述

確定好【目標的資訊】,如下圖。

建立對應的實體物件

package entity;

/**
 * 知乎使用者資訊
 * @author antgan
 *
 */
public class ZhihuUser {
    private String key;//keyword
private String name;//使用者名稱 private String identity;//身份 private String location;//所在地 private String profession;//行業 private int sex;//性別 private String school;//學校 private String major;//專業 private String recommend;//個人簡介 private String picUrl;//頭像url private int agree;//贊同 private
int thanks;//感謝 private int ask;//提問數 private int answer;//回答數 private int article;//文章數 private int collection;//收藏數 public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getIdentity() { return identity; } public void setIdentity(String identity) { this.identity = identity; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getProfession() { return profession; } public void setProfession(String profession) { this.profession = profession; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } public String getMajor() { return major; } public void setMajor(String major) { this.major = major; } public String getRecommend() { return recommend; } public void setRecommend(String recommend) { this.recommend = recommend; } public String getPicUrl() { return picUrl; } public void setPicUrl(String picUrl) { this.picUrl = picUrl; } public int getAgree() { return agree; } public void setAgree(int agree) { this.agree = agree; } public int getThanks() { return thanks; } public void setThanks(int thanks) { this.thanks = thanks; } public int getAsk() { return ask; } public void setAsk(int ask) { this.ask = ask; } public int getAnswer() { return answer; } public void setAnswer(int answer) { this.answer = answer; } public int getArticle() { return article; } public void setArticle(int article) { this.article = article; } public int getCollection() { return collection; } public void setCollection(int collection) { this.collection = collection; } @Override public String toString() { return "ZhihuUser [name=" + name + ", identity=" + identity + ", location=" + location + ", profession=" + profession + ", sex=" + sex + ", school=" + school + ", major=" + major + ", recommend=" + recommend + ", picUrl=" + picUrl + ", agree=" + agree + ", thanks=" + thanks + ", ask=" + ask + ", answer=" + answer + ", article=" + article + ", collection=" + collection + "]"; } }

編寫PageProcessor(Processor中的process方法是webmagic的核心,負責抽取目標url的邏輯

package repo;
import dao.ZhihuDao;
import dao.impl.ZhihuDaoImpl;
import entity.ZhihuUser;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

/**
 * 知乎使用者小爬蟲<br>
 * 輸入搜尋使用者關鍵詞(keyword),並把搜出來的使用者資訊爬出來<br>

 * @date 2016-5-3
 * @website ghb.soecode.com
 * @csdn blog.csdn.net/antgan
 * @author antgan
 * 
 */
public class ZhiHuUserPageProcessor implements PageProcessor{
    //抓取網站的相關配置,包括:編碼、抓取間隔、重試次數等
    private Site site = Site.me().setRetryTimes(10).setSleepTime(1000);
    //使用者數量
    private static int num = 0;
    //搜尋關鍵詞
    private static String keyword = "JAVA";
    //資料庫持久化物件,用於將使用者資訊存入資料庫
    private ZhihuDao zhihuDao = new ZhihuDaoImpl();


    /**
     * process 方法是webmagic爬蟲的核心<br>
     * 編寫抽取【待爬取目標連結】的邏輯程式碼在html中。
     */
    @Override
    public void process(Page page) {

        //1. 如果是使用者列表頁面 【入口頁面】,將所有使用者的詳細頁面的url放入target集合中。
        if(page.getUrl().regex("https://www\\.zhihu\\.com/search\\?type=people&q=[\\s\\S]+").match()){
            page.addTargetRequests(page.getHtml().xpath("//ul[@class='list users']/li/div/div[@class='body']/div[@class='line']").links().all());
        }
        //2. 如果是使用者詳細頁面
        else{
            num++;//使用者數++
            /*例項化ZhihuUser,方便持久化儲存。*/
            ZhihuUser user = new ZhihuUser();
            /*從下載到的使用者詳細頁面中抽取想要的資訊,這裡使用xpath居多*/
            /*為了方便理解,抽取到的資訊先用變數儲存,下面再賦值給物件*/
            String name = page.getHtml().xpath("//div[@class='title-section ellipsis']/span[@class='name']/text()").get();
            String identity = page.getHtml().xpath("//div[@class='title-section ellipsis']/span[@class='bio']/@title").get();
            String location = page.getHtml().xpath("//div[@class='item editable-group']/span[@class='info-wrap']/span[@class='location item']/@title").get();
            String profession = page.getHtml().xpath("//div[@class='item editable-group']/span[@class='info-wrap']/span[@class='business item']/@title").get();
            boolean isMale = page.getHtml().xpath("//span[@class='item gender']/i[@class='icon icon-profile-male']").match();
            boolean isFemale = page.getHtml().xpath("//span[@class='item gender']/i[@class='icon icon-profile-female']").match();
            int sex = -1;
            /*因為知乎有一部分人不設定性別 或者 不顯示性別。所以需要判斷一下。*/
            if(isMale&&!isFemale) sex=1;//1代表男性
            else if(!isMale&&isFemale) sex=0;//0代表女性
            else sex=2;//2代表未知
            String school =  page.getHtml().xpath("//span[@class='education item']/@title").get();
            String major = page.getHtml().xpath("//span[@class='education-extra item']/@title").get();
            String recommend =  page.getHtml().xpath("//span[@class='fold-item']/span[@class='content']/@title").get();
            String picUrl = page.getHtml().xpath("//div[@class='body clearfix']/img[@class='Avatar Avatar--l']/@src").get();
            int agree = Integer.parseInt(page.getHtml().xpath("//span[@class='zm-profile-header-user-agree']/strong/text()").get());
            int thanks = Integer.parseInt(page.getHtml().xpath("//span[@class='zm-profile-header-user-thanks']/strong/text()").get());
            int ask = Integer.parseInt(page.getHtml().xpath("//div[@class='profile-navbar clearfix']/a[2]/span[@class='num']/text()").get());
            int answer = Integer.parseInt(page.getHtml().xpath("//div[@class='profile-navbar clearfix']/a[3]/span[@class='num']/text()").get());
            int article = Integer.parseInt(page.getHtml().xpath("//div[@class='profile-navbar clearfix']/a[4]/span[@class='num']/text()").get());
            int collection = Integer.parseInt(page.getHtml().xpath("//div[@class='profile-navbar clearfix']/a[5]/span[@class='num']/text()").get());

            //物件賦值
            user.setKey(keyword);
            user.setName(name);
            user.setIdentity(identity);
            user.setLocation(location);
            user.setProfession(profession);
            user.setSex(sex);
            user.setSchool(school);
            user.setMajor(major);
            user.setRecommend(recommend);
            user.setPicUrl(picUrl);
            user.setAgree(agree);
            user.setThanks(thanks);
            user.setAsk(ask);
            user.setAnswer(answer);
            user.setArticle(article);
            user.setCollection(collection);

            System.out.println("num:"+num +" " + user.toString());//輸出物件
            zhihuDao.saveUser(user);//儲存使用者資訊到資料庫
        }
    }

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

    public static void main(String[] args) {
        long startTime ,endTime;
        System.out.println("========知乎使用者資訊小爬蟲【啟動】嘍!=========");
        startTime = new Date().getTime();
        //入口為:【https://www.zhihu.com/search?type=people&q=xxx 】,其中xxx 是搜尋關鍵詞
        Spider.create(new ZhiHuUserPageProcessor()).addUrl("https://www.zhihu.com/search?type=people&q="+keyword).thread(5).run();
        endTime = new Date().getTime();
        System.out.println("========知乎使用者資訊小爬蟲【結束】嘍!=========");
        System.out.println("一共爬到"+num+"個使用者資訊!用時為:"+(endTime-startTime)/1000+"s");
    }
}

三、處理資料 (這裡我儲存在本地資料庫中)

Dao層介面

package dao;

import entity.ZhihuUser;

/**
 * 知乎 資料持久化 介面
 * @author 甘海彬
 *
 */
public interface ZhihuDao {
    /**
     * 儲存使用者資訊
     * @param user
     * @return
     */
    public int saveUser(ZhihuUser user);
}

Dao實現類

package dao.impl;

import java.util.ArrayList;
import java.util.List;

import dao.ZhihuDao;
import entity.ZhihuUser;
import util.DBHelper;

/**
 * 知乎 資料庫持久化介面 實現
 * @author 甘海彬
 *
 */
public class ZhihuDaoImpl implements ZhihuDao{
    @Override
    public int saveUser(ZhihuUser user) {
        DBHelper dbhelper = new DBHelper();
        StringBuffer sql = new StringBuffer();
        sql.append("INSERT INTO spider_zhihu_user ( `key`,`name`,identity,location,profession,sex,school,major,recommend,picUrl,agree,thanks,ask,answer,article,collection)")
        //`key`,`name`,identity,location,profession,sex,school,major,recommend,picUrl,agree,thanks,ask,answer,article,collection
        .append("VALUES (? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ");
        //設定 sql values 的值
        List<String> sqlValues = new ArrayList<>();
        sqlValues.add(user.getKey());
        sqlValues.add(user.getName());
        sqlValues.add(user.getIdentity());
        sqlValues.add(user.getLocation());
        sqlValues.add(user.getProfession());
        sqlValues.add(""+user.getSex());
        sqlValues.add(user.getSchool());
        sqlValues.add(user.getMajor());
        sqlValues.add(user.getRecommend());
        sqlValues.add(user.getPicUrl());
        sqlValues.add(""+user.getAgree());
        sqlValues.add(""+user.getThanks());
        sqlValues.add(""+user.getAsk());
        sqlValues.add(""+user.getAnswer());
        sqlValues.add(""+user.getArticle());
        sqlValues.add(""+user.getCollection());
        try{
            int result = dbhelper.executeUpdate(sql.toString(),sqlValues);
        }catch(Exception e){
        }finally{
            dbhelper.close();
        }
        return result;
    }
}

這裡我封裝了個DbHelpler類,方便進行持久化操作,使用單例模式,併線程同步。

package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * JDBC通用類
 * @author GANAB
 *
 */
public class DBHelper {
    public static final String driver_class = "oracle.jdbc.OracleDriver";
    public static final String driver_url = "jdbc:oracle:thin:@ita-031-w7:1521:xe";
    public static final String user = "abel";
    public static final String password = "123";

    private static Connection conn = null;
    private PreparedStatement pst = null;
    private ResultSet rst = null;

    public DBHelper() {
        try {
            conn = getConnInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Connection getConnInstance() {
        if(conn == null){
            try {
                Class.forName(driver_class);
                conn = DriverManager.getConnection(driver_url, user, password);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            System.out.println("Connect success.");
        }
        return conn;
    }

    public void close() {
        try {
            if (pst != null) {
                this.pst.close();
            }
            if (rst != null) {
                this.rst.close();
            }
            if (conn != null) {
                conn.close();
            }
            System.out.println("Close connection success.");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * query
     * 
     * @param sql
     * @param sqlValues
     * @return ResultSet
     */
    public ResultSet executeQuery(String sql, List<String> sqlValues) {
        try {
            pst = conn.prepareStatement(sql);
            if (sqlValues != null && sqlValues.size() > 0) {
                setSqlValues(pst, sqlValues);
            }
            rst = pst.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rst;
    }

    /**
     * update
     * 
     * @param sql
     * @param sqlValues
     * @return result
     */
    public int executeUpdate(String sql, List<String> sqlValues) {
        int result = -1;
        try {
            pst = conn.prepareStatement(sql);
            if (sqlValues != null && sqlValues.size() > 0) {
                setSqlValues(pst, sqlValues);
            }
            result = pst.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * sql set value
     * 
     * @param pst
     * @param sqlValues
     */
    private void setSqlValues(PreparedStatement pst, List<String> sqlValues) {
        for (int i = 0; i < sqlValues.size(); i++) {
            try {
                pst.setObject(i + 1, sqlValues.get(i));
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

噢!對了!表的建立sql也提供一下!

CREATE TABLE `spider_zhihu_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key` varchar(30) DEFAULT NULL,
  `name` varchar(30) DEFAULT NULL,
  `identity` varchar(100) DEFAULT NULL,
  `location` varchar(20) DEFAULT NULL,
  `profession` varchar(30) DEFAULT NULL,
  `sex` int(2) DEFAULT NULL,
  `school` varchar(30) DEFAULT NULL,
  `major` varchar(30) DEFAULT NULL,
  `recommend` varchar(100) DEFAULT NULL,
  `picUrl` varchar(255) DEFAULT NULL,
  `agree` int(11) DEFAULT NULL,
  `thanks` int(11) DEFAULT NULL,
  `ask` int(11) DEFAULT NULL,
  `answer` int(11) DEFAULT NULL,
  `article` int(11) DEFAULT NULL,
  `collection` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;

以上就是全部程式碼。

進行測試。

這裡寫圖片描述
這裡寫圖片描述

完美~

你知道可以拿這些資料做什麼呢?
科科,我也不知道。