Tutorial: How to Build LinkedIn Scraper with Python With a Code Example

LinkedIn is the most popular social media platform for people to meet one another in a professional setting. I recommend you to read our article, How To Automatically Track LinkedIn Job Changes and How Salesforge.ai Integrates Rich Prospecting Data With ChatGPT To Automatically Personalize Emails, to illustrate the needs of LinkedIn automation for scaling up your business.

In this tutorial, I will guide you with code to get LinkedIn profile details from a list of LinkedIn Profile URL. While this tutorial focuses only on getting the profile details you need, data processing can be done according to your needs.

This tutorial will show you two ways of doing it:

sequential and asynchronous

Setting up prerequisites

  • Python 3
  • A Proxycurl API key

For sequential method:

For asynchronous method:

How to get a Proxycurl LinkedIn API credential

You can create a free API Key for Proxycurl API at Proxycurl's website. However, with the trial, your credits are limited to 10 credits (1 credit for 1 successful profile request).

If you need more credits, it is only $0.01 per profile! Read the introduction to Proxycurl API here and please send an email to hello@nubela.co for inquiries.

Sequential vs Asynchronous

The asynchronous method gives a shorter overall duration than the sequential method as it sends multiple requests at once while the sequential method only sends one request at a time, each request waiting on the previous one to return a response. However, it also depends on the request execution time that varies according to the latency of the server. As such, this tutorial provides both sequential and asynchronous methods to give comparisons of the code between them.

Let's start with viewing the base code from Proxycurl's documentation :


import requests

api_endpoint = 'https://nubela.co/proxycurl/api/linkedin'

linkedin_profile_url = 'https://www.linkedin.com/in/williamhgates'

api_key = '********-****-****-****-************' # your api_key

header_dic = {'Authorization': 'Bearer ' + api_key}

response = requests.get(api_endpoint,

                        params={'url': linkedin_profile_url},

                        headers=header_dic)

print(response.content)  # To get all profile details

print(response.content["first_name"])  # To get profile first name

You can change the "first_name" to other respond key in this Proxycurl documentation

1. Sequential Method


import requests , json 

from requests.exceptions import HTTPError

api_endpoint = 'https://nubela.co/proxycurl/api/linkedin'

api_key = '********-****-****-****-************' # your api_key

header_dic = {'Authorization': 'Bearer ' + api_key}

linkedin_profile_list = [

    'https://www.linkedin.com/in/williamhgates',

    'https://www.linkedin.com/in/melindagates',

    'https://www.linkedin.com/in/owinfrey'

                        ]

def get_profile_details(likedin_profile_url, session):

    url = api_endpoint

    response = None

    try:

        response = session.get(url, params={'url': linkedin_profile_url}, headers=header_dic)

        response.raise_for_status()

        # print(f"Response status ({url}): {response.status_code}")

    except HTTPError as http_err:

        print(f"HTTP error occurred: {http_err}")

    except Exception as err:

        print(f"An error ocurred: {err}")

    response_json = json.loads(response.content)

    return response_json

with requests.Session() as session:

    for linkedin_profile_url in linkedin_profile_list:

        try:

            response = get_profile_details(linkedin_profile_url, session)

            print (response["first_name"]) # To get first_name key

            # print(response) # To get all profile details

            # print()

        except Exception as err:

            print(f"Exception occured: {err}")

Let's breakdown the code.

As usual, we imported the required library.

Then we use all the variables defined before in the base code except the linkedin_profile_url. Instead, we create a new list, linkedin_profile_list, to hold the URLs of the profiles we want to scrape. For now, we will use 3 LinkedIn profiles for demonstration purposes.

Next, we define get_profile_details function to make a GET request to Proxycurl's LinkedIn API. We pass in the profile URL as a query param of the request, along with the Authorization header. Then the JSON response is parsed into Python dictionary using json.loads().

Uncomment line with print(f"Response status ({url}): {response.status_code}") to print response status (200 for success). Take note that a request with a 200 response code indicates a successful request and 1 credit is consumed.

The code block under with requests.Session() as session will iterate through the linkedin_profile_list and print the public_identifier.

Uncomment line with print (response) to print all profile details.

2. Asynchronous Method


import aiohttp, asyncio, json

from aiohttp import ClientSession

from urllib.error import HTTPError

api_endpoint = 'https://nubela.co/proxycurl/api/linkedin'

api_key = '********-****-****-****-************' # your api_key

header_dic = {'Authorization': 'Bearer ' + api_key}

linkedin_profile_list = [

    'https://www.linkedin.com/in/williamhgates',

    'https://www.linkedin.com/in/melindagates',

    'https://www.linkedin.com/in/owinfrey'

                        ]

async def get_profile_details_async(linkedin_profile_url, session):

    url = api_endpoint

    response = None

    try:

        response = await session.request(method='GET', url=url, params={'url': linkedin_profile_url}, headers=header_dic)

        response.raise_for_status()

        print(f"Response status ({url}): {response.status}")

    except HTTPError as http_err:

        print(f"HTTP error occurred: {http_err}")

    except Exception as err:

        print(f"An error ocurred: {err}")

    response_json = await response.content.read()

    return json.loads(response_json)

async def run_program(linkedin_profile_url, session):

    try:

        response = await get_profile_details_async(linkedin_profile_url, session)

        print (response["public_identifier"])

        print(response)

        print()

    except Exception as err:

        print(f"Exception occurred: {err}")

        pass

async def run_async():

    async with ClientSession() as session:

        await asyncio.gather(*[run_program(linkedin_profile_url, session) for linkedin_profile_url in linkedin_profile_list])

def main():

    loop = asyncio.get_event_loop()

    loop.run_until_complete(run_async())

    loop.close()

if __name__ == '__main__':

    main()

note:   if you got this error on Mac OS:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

In the terminal, try to run:

pip install --upgrade certifi

If it doesn't work, try to run:

open /Applications/Python\ 3.6/Install\ Certificates.command

Let's breakdown the code.

The async keyword that prepends the function signature tells Python that this function is a coroutine.

The await keyword, such as in response = await session.request(...) and response_json = await response.content.read(), tell that coroutine to suspend execution and give back control to the event loop, while the operation is awaiting finishes.

A coroutine is similar to generators in Python that consume values instead of producing values. It will pause the execution while waiting for the new data.

In our case, it suspends the execution of get_profile_details_async while the request is being performed: await session.request(...). It is suspended again, while the response is being read by the stream reader: await response.content.read() and json.loads(response_json).

Then, we have the run_program coroutine as a wrapper around the pipeline of getting a response from the API, parsing it to JSON, and printing the results on the screen. It awaits the execution of the get_profile_details_async coroutine.

After that, using the asyncio.gather syntax, we tell the program to schedule all the tasks based on the list of coroutines we provided. This is what allows us to execute tasks concurrently.

Lastly, define main function to run run_async function using asyncio.get_event_loop() and run it under if __name__ == '__main__' block.

3. Challenges

There are still many things you can do beyond this tutorial after retrieving the data you need. You can either store all the data first into any type of file you want and do the data processing, or you can filter the data by adding functions to our code before and only saving the information you need, to make your own personalized LinkedIn automation tool!