電影中的社工高手可以很輕易操控目標對象,使其願意去做某項事(科幻片拍的像玄幻片…),雖然現實沒有那么玄幻,但是真實的社工,或者你想要了解某個人,必然會有一個環節就是通過社交網絡之類的對目標對象進行建模,即用一些特征來描述目標對象(也可想象為對人貼標簽)。舉一個例子我們平時如果加了一個新的QQ好友,可能會去QQ空間、騰訊微博等看下他以往的歷史記錄來確認他是個怎樣的人,因為人的思維通常比較簡單,所以當接觸到一個陌生的人時急切需要在他身上尋找符合某個標簽的某個特征,一旦這家伙顯現出來某個特征,好,趕緊把對應的標簽給他貼上,這樣貼幾個標簽可以確定以后與其相處的策略,雖然可能比較偏激,但大多數人基本上都是這么干的。雖然你們可能沒有說過幾句話,但經過這么一番調查之后你已經對這個人有了一些比較深入的了解,至少是自以為有深入了解 :)
所以社工這玩意兒基本人人都會,說白了就是信息收集,如果是經常在網絡上活動的人,留下的痕跡比較多,甚至有可能畫出一條時間軸,能夠大致描述他的人生軌跡,這個可以對自己感興趣的人試一試,個人推薦使用時間軸作為主線的方式來分析。
對於信息收集,其中一個比較小的點就是如何分析一個人的作息規律,從而推測出特定的時間點他很有可能在做什么,比如統計某個人習慣在什么時間點刷知乎、泡論壇之類的,這個我有一個思路就是對於某類信息比較大,比如論壇上的留言,知乎上的點贊之類,可以通過爬蟲技術先采集再分析。我想對自己有個更深刻的了解,所以這里就用一個例子,寫一個小小的爬蟲來分析我發博客的時間分布情況。
首先確定要抓取的字段,其實只需要發布時間字段就可以了,但是想到為了以后可能還會有其它的分析,就把其它方便抓取的字段比如閱讀量、評論數之類一並抓取了。
PostMetaInfo:
package cc11001100.crawler; import java.util.Date; /** * @author CC11001100 */ public class PostMetaInfo { private String id; private String title; private Date postTime; // like uv but not private Integer visited; private Integer reviewNum; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Date getPostTime() { return postTime; } public void setPostTime(Date postTime) { this.postTime = postTime; } public Integer getVisited() { return visited; } public void setVisited(Integer visited) { this.visited = visited; } public Integer getReviewNum() { return reviewNum; } public void setReviewNum(Integer reviewNum) { this.reviewNum = reviewNum; } }
CnBlogPostMetaInfoCrawler:
package cc11001100.crawler; import com.alibaba.fastjson.JSON; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; /** * 博客園活躍時間統計 * * @author CC11001100 */ public class CnBlogPostMetaInfoCrawler { private static final Logger log = LogManager.getLogger(CnBlogPostMetaInfoCrawler.class); private static void grabMetaInfoByUserName(String username) { final String savePath = getSavePathByUserName(username); final String urlPattern = String.format("http://www.cnblogs.com/%s/default.html?page=", username); for (int pageNum = 1; true; pageNum++) { String currentPageListUrl = urlPattern + pageNum; List<PostMetaInfo> parseResultList = parsePostList(currentPageListUrl); if (parseResultList.isEmpty()) { break; } save(parseResultList, savePath); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { log.info("InterruptedException"); } } } private static String getSavePathByUserName(String username) { return "CnBlog_" + username + "_post_meta_info.data"; } private static void save(List<PostMetaInfo> postMetaInfoList, String savePath) { List<String> lineList = postMetaInfoList.stream().map(JSON::toJSONString).collect(toList()); try { FileUtils.writeLines(new File(savePath), "UTF-8", lineList, "\n", true); } catch (IOException e) { log.info("save list to file={} failed", savePath); } } private static List<PostMetaInfo> parsePostList(String listUrl) { String htmlResponse = getHtml(listUrl); if (StringUtils.isBlank(htmlResponse)) { log.error("url={}, cannot get html content", listUrl); return Collections.emptyList(); } Document document = Jsoup.parse(htmlResponse); return document.select(".postDesc").stream().map(elt -> { PostMetaInfo postMetaInfo = new PostMetaInfo(); String text = elt.ownText(); String id = extractByPattern(elt.select(">a").attr("href"), "postid=(\\d+)"); postMetaInfo.setId(id); String title = document.select(String.format("a.postTitle2[href~=/p/%s.html]", postMetaInfo.getId())).text(); postMetaInfo.setTitle(title); Date postTime = extractByPatternAndToDate(text, "posted @ (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})", "yyyy-MM-dd HH:mm"); postMetaInfo.setPostTime(postTime); postMetaInfo.setVisited(extractByPatternAndToInt(text, "閱讀\\((\\d+)\\)")); postMetaInfo.setVisited(extractByPatternAndToInt(text, "評論\\((\\d+)\\)")); return postMetaInfo; }).collect(toList()); } private static String extractByPattern(String content, String pattern) { Matcher matcher = Pattern.compile(pattern).matcher(content); if (matcher.find()) { return matcher.group(1); } return ""; } private static int extractByPatternAndToInt(String content, String extractPattern) { String rawString = extractByPattern(content, extractPattern); try { return Integer.parseInt(rawString); } catch (NumberFormatException e) { log.info("NumberFormatException, string={}", rawString); } return 0; } private static Date extractByPatternAndToDate(String content, String extractPattern, String dateFormatPattern) { String rawString = extractByPattern(content, extractPattern); try { return new SimpleDateFormat(dateFormatPattern).parse(rawString); } catch (ParseException e) { log.info("SimpleDateFormat parse exception, string={}", rawString); } return null; } private static String getHtml(String url) { final int DEFAULT_RETRY_TIMES = 10; for (int i = 1; i <= DEFAULT_RETRY_TIMES; i++) { try { return Jsoup.connect(url).execute().body(); } catch (IOException e) { log.info("url={}, retry={}", url, i); } } return ""; } private static void show(final String username) { final String savePath = getSavePathByUserName(username); try { List<String> postMetaInfoList = FileUtils.readLines(new File(savePath), "UTF-8"); postMetaInfoList.stream().map(postMetaInfo -> JSON.parseObject(postMetaInfo, PostMetaInfo.class).getPostTime().getHours()) .collect(Collectors.groupingBy(x -> x)) .forEach((k, v) -> { // 柱狀圖總共使用1000個小條條,根據屏幕大小進行調節選擇最舒適的視圖 int length = (int) (v.size() * 1000.0 / postMetaInfoList.size()); System.out.printf("%2s : %s %d\n", k, StringUtils.repeat("=", length), v.size()); }); } catch (IOException e) { log.info("read file error, path={}", savePath); } } public static void main(String[] args) { final String username = "CC11001100"; grabMetaInfoByUserName(username); show(username); } }
上面的代碼做的事情就是爬取某個人的所有博客,並將發布時間以小時為單位做一個group by … count,然后使用橫向的柱狀圖畫一個簡單的圖表,我的圖表如下:
時間大部分集中在23:00~02:00之間,從上面的圖表可以看出這個家伙很經常熬夜,由此惡毒的猜測他應該需要搞點枸杞泡着喝 :(
.