1
+ #!/usr/bin/env python2
2
+
3
+ # InSpy - A LinkedIn employee enumerator
4
+ # This script enumerates employees from any organization
5
+ # using LinkedIn. Please note that this will not harvest all
6
+ # employees within a given organization.
7
+ #
8
+ # This program is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+ # Author: Jonathan Broche
22
+ # Contact: @g0jhonny
23
+ # Version: 1.0.1
24
+ # Date: 2015-11-22
25
+ #
26
+ # usage: ./inspy.py -c <company> [-d dept/title] [-e email output format] [-i input file with dept/titles] [-o output file]
27
+ # example: ./inspy.py -c abc -e flast@abc.com -o abc_employees.txt
28
+
29
+
30
+ import requests , BeautifulSoup , argparse , signal , time , datetime , os
31
+
32
+ start_time = time .time ()
33
+
34
+ class colors :
35
+ lightblue = "\033 [1;36m"
36
+ blue = "\033 [1;34m"
37
+ normal = "\033 [0;00m"
38
+ red = "\033 [1;31m"
39
+ yellow = "\033 [1;33m"
40
+ white = "\033 [1;37m"
41
+ green = "\033 [1;32m"
42
+
43
+ #----------------------------------------#
44
+ # HARVEST USERS #
45
+ #----------------------------------------#
46
+
47
+ def inspy_enum (company , dept , ifile ):
48
+ try :
49
+ dept_dictionary = ['sales' , 'marketing' , 'human resources' , 'finance' , 'accounting' , 'inventory' , 'quality assurance' , 'insurance' , 'licenses' , 'operational' , 'customer service' , 'staff' , 'research & development' , 'management' , 'administration' , 'engineering' , 'it' , 'is' , 'strategy' , 'other' ]
50
+
51
+ employees = {}
52
+
53
+ if dept is not None :
54
+ dept_dictionary = [dept .lower ()]
55
+
56
+ if ifile is not None :
57
+ try :
58
+ if os .path .exists (ifile ):
59
+ with open (ifile , 'r' ) as f :
60
+ dept_dictionary = []
61
+ for line in f .readlines ():
62
+ if line .rstrip ():
63
+ dept_dictionary .append (line .rstrip ())
64
+ except IOError as e :
65
+ print "{}[!]{} Problem opening the file. {}" .format (e )
66
+
67
+ for dd in dept_dictionary :
68
+ print "{}[*]{} Searching for employees working at {} with '{}' in their title" .format (colors .lightblue , colors .normal , company , dd )
69
+
70
+ try :
71
+ response = requests .get ('https://www.linkedin.com/title/{}-at-{}' .format (dd .replace ('-' , ' ' ), company .replace ('-' , ' ' )), timeout = 2 )
72
+ if response .status_code == 200 :
73
+ soup = BeautifulSoup .BeautifulSoup (response .text )
74
+ else :
75
+ pass
76
+ except requests .exceptions .Timeout :
77
+ print "{}[!]{} Timeout enumerating the {} department" .format (colors .red , colors .normal , dd )
78
+ except requests .exceptions .ConnectionError :
79
+ print "{}[!]{} Connection error." .format (colors .red , colors .normal )
80
+ except requests .exceptions .HTTPError :
81
+ print "{}[!]{} HTTP error." .format (colors .red , colors .normal )
82
+
83
+ #get employee names
84
+ for n , t in zip (soup .findAll ('h3' , { "class" : "name" }), soup .findAll ('p' , { "class" : "headline" })):
85
+ name = u'' .join (n .getText ()).encode ('utf-8' )
86
+ title = u'' .join (t .getText ()).encode ('utf-8' ).replace ('&' , '&' )
87
+
88
+ if not name in employees :
89
+ employees [name ] = title
90
+
91
+ return employees
92
+ except Exception as e :
93
+ print "{}[!]{} Error harvesting users. {}" .format (colors .red , colors .normal , e )
94
+
95
+ #----------------------------------------#
96
+ # EMAILS #
97
+ #----------------------------------------#
98
+
99
+ def format_email (names , eformat ):
100
+ emails = []
101
+ for name in names :
102
+ spaces = []
103
+ for x ,y in enumerate (name ):
104
+ if ' ' in y :
105
+ spaces .append (x )
106
+
107
+ if eformat [:eformat .find ('@' )] == 'flast' :
108
+ emails .append ('{}{}{}' .format (name [0 ], name [(spaces [- 1 ]+ 1 ):], eformat [eformat .find ('@' ):]))
109
+ elif eformat [:eformat .find ('@' )] == 'lfirst' :
110
+ emails .append ('{}{}{}' .format (name [spaces [- 1 ]+ 1 ], name [0 :spaces [0 ]], eformat [eformat .find ('@' ):]))
111
+ elif eformat [:eformat .find ('@' )] == 'first.last' :
112
+ emails .append ('{}.{}{}' .format (name [0 :spaces [0 ]], name [(spaces [- 1 ]+ 1 ):], eformat [eformat .find ('@' ):]))
113
+ elif eformat [:eformat .find ('@' )] == 'last.first' :
114
+ emails .append ('{}.{}{}' .format (name [(spaces [- 1 ]+ 1 ):], name [0 :spaces [0 ]], eformat [eformat .find ('@' ):]))
115
+
116
+ return [e .lower () for e in emails ]
117
+
118
+ #----------------------------------------#
119
+ # OUTPUT #
120
+ #----------------------------------------#
121
+
122
+ def output (employees , email , company , ofile ):
123
+ counter = 0
124
+ ge , be = {}, {}
125
+ print '\n '
126
+
127
+ if email :
128
+ for k , e in zip (employees , email ):
129
+ if company in employees [k ].lower ():
130
+ if ',' in k :
131
+ be [e ] = '{}, {}' .format (k , employees [k ])
132
+ else :
133
+ ge [e ] = '{}, {}' .format (k , employees [k ])
134
+ print "{}[*]{} {}, {}, {}" .format (colors .green , colors .normal , k .replace ('&' , '&' ), employees [k ].replace ('&' , '&' ), e )
135
+ counter += 1
136
+ else :
137
+ for k in employees :
138
+ if company in employees [k ].lower ():
139
+ ge [k ] = employees [k ]
140
+ print "{}[*]{} {} {}" .format (colors .green , colors .normal , k .replace ('&' , '&' ), employees [k ].replace ('&' , '&' ))
141
+ counter += 1
142
+ if be :
143
+ print "\n {}[!]{} The following employees have commas in their names. Their emails were not accurate." .format (colors .red , colors .normal )
144
+ for k in be :
145
+ print "{}[*]{} {}" .format (colors .yellow , colors .normal , be [k ])
146
+
147
+ if ofile :
148
+ with open (ofile , 'w' ) as f :
149
+ f .write ("\n " + "-" * 69 + "\n " + "InSpy Output" + "\n " + "-" * 69 + "\n \n " )
150
+
151
+ if [e for e in ge .keys () if '@' in e ]: #if emails in keys
152
+ f .write ("\n " + "E-mails" + "\n " + "-" * 25 + "\n \n " )
153
+ for k in ge .keys ():
154
+ f .write (k + '\n ' )
155
+
156
+ f .write ("\n " + "All" + "\n " + "-" * 25 + "\n \n " )
157
+ for k in ge :
158
+ f .write ('{}, {}\n ' .format (ge [k ], k ))
159
+ else :
160
+ for k in ge :
161
+ f .write ('{}, {}\n ' .format (k , ge [k ]))
162
+
163
+ print "\n {}[*]{} Done! {}{}{} employees found." .format (colors .lightblue , colors .normal , colors .green , counter , colors .normal )
164
+ print "{}[*]{} Completed in {:.1f}s\n " .format (colors .lightblue , colors .normal , time .time ()- start_time )
165
+
166
+ #----------------------------------------#
167
+ # MAIN #
168
+ #----------------------------------------#
169
+
170
+ def main ():
171
+ print "\n " + "-" * 74 + "\n " + colors .white + "InSpy v1.0 - LinkedIn Employee Enumerator by Jonathan Broche (@g0jhonny)\n " + colors .normal + "-" * 74 + "\n "
172
+ parser = argparse .ArgumentParser (description = 'InSpy - A LinkedIn employee enumerator by Jonathan Broche (@g0jhonny)' )
173
+ parser .add_argument ('-c' , '--company' , required = True , help = 'Company name' )
174
+ parser .add_argument ('-d' , '--dept' , nargs = '?' , const = '' , help = 'Department or title to query employees against. Inspy searches through a predefined list by default.' )
175
+ parser .add_argument ('-e' , '--emailformat' , help = 'Email output format. Acceptable formats: first.last@xyz.com, last.first@xyz.com, flast@xyz.com, lastf@xyz.com' )
176
+ parser .add_argument ('-i' , '--inputfilename' , nargs = '?' , const = '' , help = 'File with list of departments or titles to query employees against (one item per line)' )
177
+ parser .add_argument ('-o' , '--outfilename' , nargs = '?' , const = '' , help = 'Output results to text file' )
178
+ args = parser .parse_args ()
179
+
180
+ employees = inspy_enum (args .company , args .dept , args .inputfilename )
181
+
182
+ if args .emailformat :
183
+ if args .emailformat .find ('@' ) and args .emailformat [:args .emailformat .find ('@' )] in {'flast' , 'lfirst' , 'first.last' , 'last.first' }:
184
+ if employees is not None :
185
+ e = format_email (employees .keys (), args .emailformat )
186
+ output (employees , e ,args .company .lower (), args .outfilename )
187
+ else :
188
+ print "{}[!]{} Please provide a valid email address format (i.e., flast@xyz.com, lfirst@xyz.com, first.last@xyz.com, last.first@xyz.com)" .format (colors .red , colors .normal )
189
+ else :
190
+ if employees is not None :
191
+ output (employees ,'' ,args .company .lower (), args .outfilename )
192
+
193
+ if __name__ == '__main__' :
194
+ main ()
0 commit comments