惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
Security Latest
Security Latest
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
H
Help Net Security
T
The Exploit Database - CXSecurity.com
Know Your Adversary
Know Your Adversary
博客园_首页
S
Securelist
S
Schneier on Security
G
GRAHAM CLULEY
Cisco Talos Blog
Cisco Talos Blog
V
Visual Studio Blog
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
有赞技术团队
有赞技术团队
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
量子位
L
Lohrmann on Cybersecurity
Hugging Face - Blog
Hugging Face - Blog
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
SegmentFault 最新的问题
The Hacker News
The Hacker News
罗磊的独立博客
博客园 - 司徒正美
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
K
Kaspersky official blog
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
L
LINUX DO - 热门话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
V
Vulnerabilities – Threatpost
AWS News Blog
AWS News Blog
小众软件
小众软件
Project Zero
Project Zero

夜行人

回家路上 第一期的直播演示项目 震动检测器 正能量 在线参观CodeLab Neverland 发布 CodeLab Adapter 3.3.1 DynamicTable 之 纸糊方向盘 CodeLab DynamicTable: 一个可实施的技术方案 CodeLab Insight 发布 Alpha 版 情人节 Home Assistant 周报 && IoT 周报 (02) Joplin: 关注隐私的 Evernote 开源替代软件 浏览器的未来与 Web 传感器 Home Assistant 周报 && IoT 周报 (01) 百宝箱(01) 论自由 介绍 WebThings Home Assistant 周报 && iot 周报 (00) 百宝箱(00) 毛姆读书心得 传世之作 周末徒步 CodeLab Adapter ❤️ Jupyter/Python 航班 躲雨 夏令营途中 [译]思想--作为一种技术 The future of coding 美国之行 三门问题的程序模拟 从Python转向Pharo https://blog.just4fun.site/post/iot/iot-open-source-projects/ Python异步编程笔记 https://blog.just4fun.site/post/iot/iot-open-source-hardware-community/ 万物积木化开发者社区 CodeLab ❤️ Blender Scratch3技术分析之云变量 API(第7篇) [译]对管道(Pipes)的偏爱 [译]提出正确的问题比得到正确答案更重要 蓝牙设备与Scratch3.0 创建你的第一个Scratch3.0 Extension Scratch3技术分析之项目内部数据(第6篇) Scratch3技术分析之社区 API(第5篇) Scratch3技术分析之User API(第4篇) Scratch3技术分析之项目主页API(第3篇) Scratch3技术分析之静态资源API(第2篇) Scratch3.0、micro:bit与Windows7 https://blog.just4fun.site/post/iot/zerynth-vs-micropython/ 核聚变、方所与半宅空间 可视化编程为何是个糟糕的主意 codelab.club周末聚会 关于codelab.club '下一件大事'是一个房间 Hungry Robot - Eat everything 编程作为一种思考方式 今日简史 史蒂夫·乔布斯传 罗素自选文集 https://blog.just4fun.site/post/edx/tianjin-scratch-ai/ https://blog.just4fun.site/post/edx/richie-cms-openedx/ 徒步武功山 WebUSB与micro:bit 积木化编程与3D场景 夜宿武功山顶 scratch3-adapter接入优必选Alpha系列机器人 https://blog.just4fun.site/post/edx/video-migration-note/ scratch3-adapter重构笔记 https://blog.just4fun.site/post/edx/edx-community-members/ 两种硬件编程风格的比较 使用micro:bit自制PPT翻页笔 柏拉图对话集 scratch3.0 + micro:bit 七月电影放映计划 非营利组织的管理 Screenly--用树莓派让任何屏幕变为可编程的数字标牌 以最佳实践开始你的Django项目 micro:bit与事件驱动 为Scratch3.0设计的插件系统(上篇) OCR应用一例 近两年读过的一些好书 blockly开发之使用python驱动浏览器中的turtle(2) 牛顿新传 文学理论入门 逻辑的引擎 人生的意义 blockly开发之生成并运行js代码(1) blockly开发之hello world(0) micro:bit使用笔记 神器之Termux https://blog.just4fun.site/post/iot/micropython-notes/ Cozmo what is this Scratch的前世今生 下段旅程 我行在远方 爆裂 途中杂记 https://blog.just4fun.site/post/edx/open-edx-startup/ cozmo系列之入门 - 有性格且可编程的机器人 PaperWeekly开发笔记 创业二三事
LinkedIn爬虫笔记
种瓜 · 2017-02-01 · via 夜行人

需求

朋友在国外留学,老板让完成一个任务: 登录LinkedIn后搜索pr关键词, 对结果做简单分析、筛选和分类,将目标信息手动摘录到Google Drive ,以便作进一步沟通,发展为client。朋友打电话抱怨说,工作量可怕,至少需要淹没一周的时间(从图中可以看到有465,276条匹配结果)。聊完,我觉得是个典型的信息采集工作,可以用爬虫完成

需求描述

我让朋友将手动操作写成伪代码,用if else写出操作逻辑,这样能避免反复沟通成本,最理想的情况是当面做演示,传送门发明之前只好作罢。

朋友之前有简单入门过编程,将任务表达得十分清晰

此处涉及隐私,操作文档就不放出了,是典型的网页信息采集任务(外加一些定制化的筛选和分类逻辑)

思路

最初想走捷径,直接调用api来取数据,在github里搜索一圈,试图找到好用LinkedIn api,逛了一圈,发现LinkedIn的库都是基于oauth2与LinkedIn通信(诸如python-linkedin),需要申请为开发者,操作挺繁琐,而且获得的数据也不够丰富

于是回退到用爬虫解决这个问题(网页所见即所得)

技术问题

初步尝试后,发现存在两个需要处理的问题

登录

要在LinkedIn的搜索前头的pr,需要先登录。

尽管我们通过在浏览器搜索pr,知道实际对应的url是:http://www.linkedin.com/search/results/index/?keywords=pr&origin=GLOBAL_SEARCH_HEADER 。但未登录会报404

简易的解决方案是模拟登陆,用requests的Session维持登录状态即可,文档见高级用法

如何模拟登录,还需需要分析下提交参数的,如果连分析都懒,google呀!这么常见的问题,赌五毛钱肯定有人遇到过!

果不其然:Logging in to LinkedIn with python requests sessions

于是我们我们便解决了登录问题(linkedin_spider.py):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
from bs4 import BeautifulSoup

client = requests.Session()

HOMEPAGE_URL = 'https://www.linkedin.com'
LOGIN_URL = 'https://www.linkedin.com/uas/login-submit'

html = client.get(HOMEPAGE_URL).content
soup = BeautifulSoup(html)
csrf = soup.find(id="loginCsrfParam-login")['value']

login_information = {
    'session_key':'username',
    'session_password':'password',
    'loginCsrfParam': csrf,
}

client.post(LOGIN_URL, data=login_information)
search_url = "http://www.linkedin.com/search/results/index/?keywords=pr&origin=GLOBAL_SEARCH_HEADER"
response = client.get('search_url')
print(response) # 200

ajax

我们查看页面内容(print(response.content))发现,搜索pr,linkedin的页面是通过ajax异步加载出的

我们运行js之后,才能拿到页面内容,我们可以使用phantomjs,这个问题我在之前爬京东时候记录过:用pyspider写的爬虫几例,pyspider集成了phantomjs

phantomjs实际上作为一个服务运行,你可以理解成一个管道,输入网页源码,输入解析完的对象

当然你也可以使用Selenium

策略

所以要完成我们的爬虫任务需要将以上两个问题的解决方案结合在一起,方法很多

  • 直接使用Selenium驱动chrome(可能最简易)
  • 在pyspider中模拟登陆(pyspider已经和phantomjs集成)
  • 将phantomjs与我们前头的代码整合,phantomjs作为服务即可,原理可以看pyspider中相关源码,也可以看这个项目:PhantomjsFetcher

至于如何写入Google Drive ,这倒是个简易的任务,诸如你可以先将采集的信息存为CSV,然后导入Google Drive即可

或者使用相关库来操作Google Drive,诸如:googledrive/PyDrive

动手

最终我选择Selenium驱动chrome的方案(通过),重要的原因之一是,朋友的需求中有一点非这么做不可:需要使用chrome插件hunter来获取LinkedIn用户的邮箱(如果缺失的话)

google出来的Selenium资料基本是在window下的使用经验。在mac下的资料不错,踩的坑不少,记录一下

前头提到我们遇到两个问题:登陆和ajax,使用Selenium的话,这两个问题似乎都不存在,然后坑在后边

安装依赖

1
2
brew install chromedriver
pip install selenium #selenium==2.52.0

先跑起来

1
2
3
4
5
6
from selenium import webdriver
driver = webdriver.Chrome() 
url = "https://www.zhihu.com"
driver.get(url)
print(driver.title)
driver.save_screenshot('/tmp/test.png') #截个图

很快我们就遇到问题。在默认情况下,每次的数据目录都是临时的,于是我们的浏览器每次都是崭新的,既没有插件,也没有任何历史记录,就算你登陆过LinkedIn,下次运行脚本也啥都没了

一番搜索,我们发现需要使用user-data-dir来指定用户数据目录,好让webdriver驱动的浏览器能和我们日常使用的一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/usr/bin/env python
# encoding: utf-8
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--user-data-dir=/Users/wwj/Library/Application Support/Google/Chrome"); # mac下 各种插件可用
driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver',chrome_options=options)
#url = "https://www.linkedin.com/feed/" #在浏览器里登陆过
url = "https://www.linkedin.com/search/results/index/?keywords=pr&origin=GLOBAL_SEARCH_HEADER" #在浏览器里登陆过
driver.get(url)
#element = driver.find_element_by_xpath('')
driver.save_screenshot('/tmp/test.png') #等待时间
#data = element.text
#print(data)

我们又发现了一个问题,ajax的渲染

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# encoding: utf-8
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
options = webdriver.ChromeOptions()
options.add_argument("--user-data-dir=/Users/wwj/Library/Application Support/Google/Chrome"); # mac下 各种插件可用
driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver',chrome_options=options)
url = "https://www.linkedin.com/search/results/index/?keywords=pr&origin=GLOBAL_SEARCH_HEADER" #在浏览器里登陆过
driver.get(url)
#element = driver.find_element_by_xpath('')
#driver.implicitly_wait(10)
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.CLASS_NAME,'search-results__title-and-total-wrapper')))#如果发现就继续往下走,所以这可以视为一种阻塞操作,文档不足,使用ipython探索,诸如By.CLASS_NAME
# 获取页面的数据 存入
driver.save_screenshot('/tmp/test.png')
driver.quit()

存储数据

开发的时候使用peewee存储,方便去重之类,之后导出为csv或是其他