Skip to main content

linkedin automation tool

Project description

Introduction

Install package

pip install linkedin_cat

Preparation

  • create file linkedin_cookies.json
  • please use Chrome Extension EditThisCookie to export linkedin cookies to linkedin_cookies.json

linkedin_cookies.json

[
{
    "domain": ".linkedin.com",
    "expirationDate": 1746423475,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_gcl_au",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "1.1.191481430.1738647475",
    "id": 1
},
{
    "domain": ".linkedin.com",
    "expirationDate": 1746089950.085703,
    ......

Example Usage:

- Linkedin Send Message

from linkedin_cat.message import LinkedinMessage

# linkedin cookie file path
linkedin_cookies_json='./linkedin_cookies.json'
# headless mode
headless = False
# Ininialized LinkedinMessage
bot = LinkedinMessage(linkedin_cookies_json,headless)

# message to sent, use FIRSTNAME or FULLNAME to customized the message
message = "Hello FIRSTNAME,hope you are doing well. Glad to e-meet with you on linkedin"

# Send single request by url
url = "https://www.linkedin.com/in/chandlersong/"
bot.send_single_request(url,message)

# Send multi request by linkedin url list 
urls_list = [    
    	"https://www.linkedin.com/in/chandlersong/",
    	"https://www.linkedin.com/in/chandlersong/",
        ]

bot.send_multi_request(urls_list,message)
    

- Python Command

cat_run.py

import argparse
from linkedin_cat.message import LinkedinMessage
import json
import time


class LocalStorageHelper:
    def __init__(self, storage_file_path):
        self.storage_file_path = storage_file_path
        self.storage = self.load_storage()

    def load_storage(self):
        try:
            with open(self.storage_file_path, "r", encoding="utf8") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}

    def save_storage(self):
        with open(self.storage_file_path, "w", encoding="utf8") as f:
            json.dump(self.storage, f)

    def set(self, key, value):
        self.storage[key] = value
        self.save_storage()

    def get(self, key):
        return self.storage.get(key)


def get_message(message_file_path):
    with open(message_file_path, "r", encoding="utf8") as f:
        message = f.read()
    return message


def read_urls_list(urls_file_path):
    with open(urls_file_path, "r", encoding="utf8") as f:
        urls_list = f.readlines()
    urls_list = [url.strip() for url in urls_list]
    return urls_list


def send_messages(urls_list, message, storage_helper, bot):
    for url in urls_list:
        if storage_helper.get(url):
            continue
        bot.send_single_request(url, message)
        storage_helper.set(url, time.time())


def main():
    parser = argparse.ArgumentParser(description='Send LinkedIn messages to a list of URLs.')
    parser.add_argument('cookies', type=str, help='Path to the LinkedIn cookies JSON file (e.g., "cookies.json").')
    parser.add_argument('message', type=str, help='Path to the message file (e.g., "message.txt").')
    parser.add_argument('urls', type=str, help='Path to the URLs file (e.g., "urls.txt").')
    parser.add_argument('button_class', type=str, help="""Message Button Class: ieSHXhFfVTxQfadOJdXYOIDuVKsBXgPtjNxI (eg:<button aria-label="Invite XXXX to connect" id="ember840"
                                                            class="artdeco-button artdeco-button--2
                                                            artdeco-button--primary ember-view ieSHXhFfVTxQfadOJdXYOIDuVKsBXgPtjNxI"
                                                            type="button">)"""
                        )
    parser.add_argument('--headless', action='store_true',
                        help='Run the bot in headless mode (without opening a browser window).')
    parser.add_argument('--storage', type=str, default='processed_urls.json',
                        help='Path to the local storage file for processed URLs (e.g., "processed_urls.json"). This file keeps track of URLs that have already been processed to avoid sending duplicate messages.')

    args = parser.parse_args()

    message = get_message(args.message)
    urls_list = read_urls_list(args.urls)

    bot = LinkedinMessage(args.cookies, args.headless,button_class=args.button_class)

    storage_helper = LocalStorageHelper(args.storage)
    send_messages(urls_list, message, storage_helper, bot)




if __name__ == "__main__":
    main()

运行脚本:

python .\cat_run.py linkedin_cookies.json msg.txt urls.txt  gUfuZBuHecrFJGhcTDwkUSEhvpaAQjXWfwDwQ 

linkedin_cookies.json (EditThisCookies导出)

msg.txt

Hello FIRSTNAME, Hope you are doing well. Glad to e-meet with you on linkedin.

urls.txt

https://www.linkedin.com/in/xxxx/
https://www.linkedin.com/in/yyyyy/
https://www.linkedin.com/in/zzzzz/
......

Message/Connect Button Class (gUfuZBuHecrFJGhcTDwkUSEhvpaAQjXWfwDwQ)

<button aria-label="Invite xxx to connect" id="ember485" class="artdeco-button artdeco-button--2 artdeco-button--primary ember-view gUfuZBuHecrFJGhcTDwkUSEhvpaAQjXWfwDwQ" type="button">        <svg role="none" aria-hidden="true" class="artdeco-button__icon " xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" data-supported-dps="16x16" data-test-icon="connect-small">
<!---->    
    <use href="#connect-small" width="16" height="16"></use>
</svg>

<span class="artdeco-button__text">
    Connect
</span></button>

无Web界面化运行

python .\cat_run.py linkedin_cookies.json msg.txt urls.txt  gUfuZBuHecrFJGhcTDwkUSEhvpaAQjXWfwDwQ --headless

- Linkedin Search

linkedin_search_keyword.py 关键词搜索

from linkedin_cat.search import LinkedinSearch

# li_class
# <li class="AnzbBoSgWFPvyPjbkRjRKkrJVxiaC"
# title_div_class
# <div class="RTuDraFdKmgRAxtLKirgHvzAIXdijkkEQ t-14 t-black t-normal"
# location_div_class
# <div class="rnkdymaxWKCNeIKKaOIbrXkZKdCeIOFLMZ"
# intro_p_class
# <p class=KtEWEpbEqyHyYWJByRbdgYQzrISPzhXvdIM
# link_span_class
# <span class=OORPFqatrHRtkxtjkKqROvaiHZJKsVvncNCM

# 实例化bot
bot = LinkedinSearch(
         linkedin_cookies_json='linkedin_cookies.json',
         headless=False,
         li_class='AnzbBoSgWFPvyPjbkRjRKkrJVxiaC',
         title_div_class='RTuDraFdKmgRAxtLKirgHvzAIXdijkkEQ',
         location_div_class='rnkdymaxWKCNeIKKaOIbrXkZKdCeIOFLMZ',
         intro_p_class='KtEWEpbEqyHyYWJByRbdgYQzrISPzhXvdIM',
         link_span_class='OORPFqatrHRtkxtjkKqROvaiHZJKsVvncNCM'
         )

# 关键词搜索
results = bot.search_keywords('wang microsoft',wait=False)

# 返回 list 结果
for result in results:
    print(result)
    # {'name': '', 'title': '', 'location': '', 'introduction': '', 'linkedin_url': '', 'image_url': ''}

linkedin_search_filters.py 多条件搜索

from linkedin_cat.search import LinkedinSearch

# 实例化bot
bot = LinkedinSearch(
         linkedin_cookies_json='linkedin_cookies.json',
         headless=False,
         li_class='AnzbBoSgWFPvyPjbkRjRKkrJVxiaC',
         title_div_class='RTuDraFdKmgRAxtLKirgHvzAIXdijkkEQ',
         location_div_class='rnkdymaxWKCNeIKKaOIbrXkZKdCeIOFLMZ',
         intro_p_class='KtEWEpbEqyHyYWJByRbdgYQzrISPzhXvdIM',
         link_span_class='OORPFqatrHRtkxtjkKqROvaiHZJKsVvncNCM'
         )

# 按照搜索条件搜索, 自定义搜索条件
# 生成搜索的url
url = bot.generate_linkedin_search_url(keywords, company=None, title=None,school=None,first_name=None, last_name=None)
# 获取搜索页面的html
html = bot.open_linkedin_url(url)
# 解析html页面
results = bot.parse_linkedin_results(html)

for result in results:
    print(result)

save_profile_by_linkedin_url.py 保存Linkedin Profile

from linkedin_cat.search import LinkedinSearch
# 实例化bot
bot = LinkedinSearch(
         linkedin_cookies_json='linkedin_cookies.json',
         headless=False,
         )
# 搜索linkedin profile,并保存为json文件
save_folder = './linkedin'
url = "https://www.linkedin.com/in/chandlersong/"
bot.search_linkedin_profile(url,save_folder)

save_profile_by_linkedin_url_list.py

from linkedin_cat.search import LinkedinSearch
# 实例化bot
bot = LinkedinSearch(
         linkedin_cookies_json='linkedin_cookies.json',
         headless=False,
         )

# 批量搜索linkedin profile,并保存为json文件
save_folder = './linkedin'
url_list = [
    "https://www.linkedin.com/in/chandlersong/"
    "https://www.linkedin.com/in/chandlersong/"
    "https://www.linkedin.com/in/chandlersong/"
    ]

bot.search_linkedin_profile_list(url_list,save_folder)

- Recruiter LLM

import os
from jinja2 import Template
from cn_detect.detect import ChineseNameDetect
from linkedin_cat.search import LinkedinSearch # 继承了LinkedinMessage
from llm_cat.deepseek_api import deepseek_chat

# 添加自定义模块路径
# import sys
# sys.path.append(r'C:\Users\xxx\pyfunc')
# from llm import llm

# LinkedIn cookie文件路径
linkedin_cookies_json = "cookies.json"
headless = True
token = "sk-xxxxxxxxxxxxxxxxxxxxxx"

# 创建LinkedinSearch对象
search_bot = LinkedinSearch(linkedin_cookies_json,headless=headless)
detector = ChineseNameDetect()

def judge_skill_prompt(mini_profile):
    # 构建评估候选人的模板
    prompt_template = """
                            Please assess if the candidate in #### is a security engineer. 
                            If yes, return True; if not, return False. 
                            Provide the response in JSON format. 
                            example:
                            {
                                "result": True
                            }
                            \n\n 
                            ####{{ short_profile }}####
                        """
    template = Template(prompt_template)
    prompt = template.render(short_profile=mini_profile)

    # 使用LLM评估候选人
    # reply = llm(prompt)
    reply = deepseek_chat(prompt, token)
    print(reply)
    return reply


def search_recruiter(keywords, company=None, title=None, school=None, first_name=None, last_name=None,
                     chinese_detect=True, send_message=True, save_resume=True,
                     message=f"Hello FIRSTNAME, hope you are doing well. Glad to e-meet with you on LinkedIn."):
    page = 1
    while True:
        # 生成LinkedIn搜索URL
        url = search_bot.generate_linkedin_search_url(keywords, company, title, school, first_name, last_name)
        if page != 1:
            url += f'&page={page}'

        # 打开LinkedIn URL并解析结果
        html = search_bot.open_linkedin_url(url)
        results = search_bot.parse_linkedin_results(html)

        # 如果没有结果,退出循环
        if not results:
            print('No results')
            break

        print('Total Results:', len(results))
        for result in results:
            print(result)
            try:
                name = result['name']
                url = result['linkedin_url'].split('?')[0]

                mini_profile = {key: result[key] for key in ['title', 'introduction', 'location']}

                # 判断linkedin是否存在
                filename = search_bot.extract_username_from_linkedin_url(url) + '.json'
                if os.path.exists(os.path.join('./linkedin', filename)):
                    print(f'{filename} already exists, skipping...')
                    continue

                # 判断是否需要检测中文
                should_continue = not chinese_detect or detector.detect_chinese_word(name) or detector.detect_family_name(name)

                if should_continue:
                    reply = judge_skill_prompt(mini_profile)
                    if 'True' in reply:
                        # 发送消息
                        if send_message:
                            search_bot.send_single_request(url, message)
                        # 保存简历
                        if save_resume:
                            search_bot.search_linkedin_profile(url, thread_pool=True)

            except Exception as e:
                print(f"Error: {e}")

        # 翻页
        page += 1

# 使用示例
search_recruiter('security', company='google',last_name='jiang',send_message=False,save_resume=True)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

linkedin_cat-0.1.0.tar.gz (32.6 kB view details)

Uploaded Source

File details

Details for the file linkedin_cat-0.1.0.tar.gz.

File metadata

  • Download URL: linkedin_cat-0.1.0.tar.gz
  • Upload date:
  • Size: 32.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.10.11

File hashes

Hashes for linkedin_cat-0.1.0.tar.gz
Algorithm Hash digest
SHA256 165762070375832c985d1cbca8e501d9e1e492a2b3a357a3402d37dfcefa4192
MD5 5239119e47d31c052f195fd7353d74b4
BLAKE2b-256 3216362bb928270dd2d2133363b470f5758902984fdafb92bbcf827419119ae4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page