爬蟲實踐-基於Jsoup爬取Facebook羣組成員信息

基於Jsoup爬取Facebook羣組成員信息

咱們知道,相似今日頭條、UC頭條這類的App,其內容絕大部分是來源於爬蟲抓取。咱們可使用不少語言來實現爬蟲,C/C++、Java、Python、PHP、NodeJS等,經常使用的框架也有不少,像Python的Scrapy、NodeJS的cheerio、Java的Jsoup等等。本文將演示如何經過Jsoup實現Facebook模擬登陸,爬取特定羣組的成員信息,並導出Excel。php

Keywords: Netbeans, JSwing, Jsoup, Apache POI , Jacksonhtml

Source Code: FacebookGrabberjava

1. Facebook模擬登陸

想要爬取Facebook上面的羣組成員信息,第一步須要先進行登陸,並將登陸成功後的cookie保存,以後每次請求的headers中都要帶上該cookie用於用戶識別。git

訪問https://www.facebook.com/login 經過chrome檢查html元素能夠看到: github

Facebook登陸頁面

知道對應的登陸url以及請求參數以後,如今咱們經過Jsoup來構造登陸請求以獲取用戶cookie信息。ajax

這裏,我寫了一個基類,方便請求調用的同時,自動將每次請求獲取到的cookie保存並附帶到下一次的請求當中:chrome

private Connection getConnection(String url) {
        return Jsoup.connect(url)
                .timeout(TIMEOUT_CONNECTION)
                .userAgent(userAgent)
                .followRedirects(true)
                .ignoreContentType(true);
    }

    protected Document requestDocument(String url, String httpMethod, Map<String, String> data) throws Exception {
        Connection connection = getConnection(url);
        if (data != null && data.size() > 0) {
            connection.data(data);
        }
        if (cookies != null) {
            connection.cookies(cookies);
        }
        Document resultDocument = HTTP_POST.equalsIgnoreCase(httpMethod) ? connection.post() : connection.get();
        return resultDocument;
    }

    protected Response requestBody(String url, String httpMethod, Map<String, String> data) throws Exception {
        Connection connection = getConnection(url);
        if (data != null && data.size() > 0) {
            connection.data(data);
        }
        if (cookies != null) {
            connection.cookies(cookies);
        }
        connection.method(HTTP_POST.equalsIgnoreCase(httpMethod) ? Connection.Method.POST : Connection.Method.GET);
        Connection.Response res = connection.execute();
        if (res.cookies() != null && !res.cookies().isEmpty()) {
            cookies = res.cookies();
        }
        return res;
    }
複製代碼

基於封裝好的請求基類,接下來實現 模擬登陸 就變得更加簡單了:apache

public FbUserInfo login(String email, String pass) throws Exception {
        // Urls.LOGIN = "https://www.facebook.com/login.php?login_attempt=1";
        Response initResponse = requestBody(Urls.LOGIN, HTTP_GET, null); //fetching cookie and saving

        Map<String, String> loginParams = new HashMap<>();
        loginParams.put("email", email);
        loginParams.put("pass", pass);
        Response loginResponse = requestBody(Urls.LOGIN, HTTP_POST, loginParams);
        Document loginDoc = loginResponse.parse();

        FbUserInfo userInfo = null;
        String userId = loginResponse.cookies().get("c_user"); // current login userId
        if (userId != null && userId.length() > 0) {
            userInfo = new FbUserInfo();
            userInfo.setId(userId);
        }
        return userInfo;
    }
複製代碼

2. 獲取羣組中的管理員以及成員列表

下面將以 Homesteads.and.Sustainability 爲例,演示如何獲取對應羣組中的管理員以及成員列表。json

訪問https://www.facebook.com/groups/Homesteads.and.Sustainability/members/ ,經過查看chrome的network中的請求和返回的response進行分析: cookie

members_response

將response中的html拷貝出來,經過查找首次加載頁面中顯示的人名,定位到管理員和成員對應的html信息以下(位於id爲groupsMemberBrowser的Element中):

members_html

格式化文本後,再進行詳細的分析: 區分管理員和普通成員,以及獲取他們的id,能夠經過下圖所示的前綴規則識別出來:

admins_members

大體代碼以下:

private List<FbGroupUserInfo> getMembers(Element groupsMembersElement, int role) {
        String prefix = role == Constants.GROUP_ROLE_ADMIN ? "admins_moderators_" : "recently_joined_";
        List<FbGroupUserInfo> userInfoList = new ArrayList<>();
        Elements memberElements = groupsMembersElement.select(String.format("div[id^=%s]", prefix));
        if (memberElements != null && memberElements.size() > 0) {
            for (Element e : memberElements) {
                String id = e.attr("id").replace(prefix, "");
                String nickName = e.select("a img").first().attr("aria-label");
                String userName = e.select("a").first().attr("href").split("\\?")[0];
                userName = userName.substring(userName.lastIndexOf("/"));
                Elements joinElements = e.getElementsByClass("_60rj");
                String joinDate = joinElements.size() > 0 && role == Constants.GROUP_ROLE_GENERAL
                        ? joinElements.first().text().trim() : "";
                FbGroupUserInfo userInfo = new FbGroupUserInfo();
                userInfo.setId(id);
                userInfo.setNickName(nickName);
                userInfo.setUserName(userName);
                userInfo.setJoinInfo(joinDate);
                userInfo.setRole(role);
                userInfoList.add(userInfo);
            }
        }
        return userInfoList;
    }
複製代碼

上面咱們只是經過解析首次訪問的html文本獲取到對應的成員信息,可是咱們知道羣組中的成員是遠遠不止這幾個的,咱們經過觸發網頁頁面中的加載更多能夠看到,每次往下,都會發起請求獲取更多成員信息:

request_more

那麼,這個請求更多數據的url從哪裏獲取呢?回頭看首次返回的html,能夠看到:

more_url

獲取加載更多的url代碼大體以下:

private String getMoreItemUrl(Element groupsMembersElement, int role) {
        String prefix = role == Constants.GROUP_ROLE_ADMIN ? "group_admins_moderators" : "group_confirmed_members";
        String moreItemUrl = "";
        try {
            moreItemUrl = groupsMembersElement.select(String.format("a[href^=/ajax/browser/list/%s/]", prefix))
                    .first().attr("href");
        } catch (Exception e) {
        }
        return moreItemUrl;
    }
複製代碼

第一次咱們經過分析首次的html能夠知道第一批成員以及第一個加載更多的url,那麼接下來第二次以及以後每次返回的都是json數據了。一樣的,經過分析返回的json格式,能夠看到,json中的成員信息也是以html的文本返回的,依葫蘆畫瓢,不斷循環直到沒有加載更多的url,這樣就能夠獲取到全部成員的id和nickname了。

獲取ajax返回數據中的groupMembersElement,大致代碼以下

ajaxString = ajaxString.substring(ajaxString.indexOf("{"));
 ObjectMapper m = new ObjectMapper();
 JsonNode rootNode = m.readValue(ajaxString, JsonNode.class);
 String html = rootNode.get("domops").get(0).get(3).get("__html").getValueAsText();
 Element groupMembersElement = Jsoup.parse(html, "", Parser.xmlParser());
複製代碼

可是,只是獲取到成員的id和nickname還不夠,咱們須要獲取到成員更詳細的信息:故鄉、居住地、性別等。

3.獲取用戶詳細信息

下面以 own a salon 這個Facebook用戶爲例,演示如何獲取用戶詳細信息。

這裏經過訪問手機版頁面(手機版頁面獲取信息更方便)https://m.facebook.com/profile.php?v=info&id=100005502877898

profile mobile site
profile current city

很明顯的,咱們能夠經過html中的關鍵字來獲取對應的故鄉、居住地、性別等。大體代碼以下:

public FbUserInfo getUserInfo(String userId) throws Exception {
        // Urls.USER_PROFILE = https://m.facebook.com/profile.php?v=info&id=%s;
        String url = String.format(Urls.USER_PROFILE, userId);
        Document doc = requestDocument(url, HTTP_GET, null);

        Elements userNameElements = doc.select("div[title=Facebook] div div");
        Elements genderElements = doc.select("div[title=Gender] div div");
        Elements hometownElements = doc.select("h4:contains(Home Town)");
        Elements locationElements = doc.select("h4:contains(Current City)");

        String userName = userNameElements.size() > 0 ? userNameElements.first().text().trim() : "";
        String gender = genderElements.size() > 0 ? genderElements.first().text().trim() : "";
        String hometown = hometownElements.size() > 0 ? hometownElements.first().firstElementSibling().text().trim() : "";
        String location = locationElements.size() > 0 ? locationElements.first().firstElementSibling().text().trim() : "";

        FbUserInfo userInfo = new FbUserInfo();
        userInfo.setId(userId);
        userInfo.setUserName(userName);
        userInfo.setGender(gender);
        userInfo.setHometown(hometown);
        userInfo.setLocation(location);
        return userInfo;
    }
複製代碼

4.最終效果

主界面及導出數據
相關文章
相關標籤/搜索