Skip to content

Commit

Permalink
Merge pull request #37 from tomquirk/travis
Browse files Browse the repository at this point in the history
init pypi deploy travis
  • Loading branch information
tomquirk authored Mar 26, 2019
2 parents b979d49 + 04865b4 commit eac2f5e
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 43 deletions.
13 changes: 0 additions & 13 deletions .travis.yml

This file was deleted.

8 changes: 4 additions & 4 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ if err:

### linkedin.mark_conversation_as_seen(conversation_urn_id)

Mark a given conversation as seen.
Mark a given conversation as seen.

**Arguments**

Expand Down Expand Up @@ -410,8 +410,8 @@ results = linkedin.search_people(
keywords='software,lol',
connection_of='AC000120303',
network_depth='F',
regions=[4909],
industries=[29, 1]
regions=['au:4909'],
industries=['29', '1']
)
```

Expand Down Expand Up @@ -450,4 +450,4 @@ invite_to_ignore = linkedin.get_invitations()[1]

linkedin.reply_invitation(invitation_entity_urn=invite_to_accept['entityUrn'], invitation_shared_secret=invite_to_accept['sharedSecret'])
linkedin.reply_invitation(invitation_entity_urn=invite_to_ignore['entityUrn'], invitation_shared_secret=invite_to_ignore['sharedSecret'], action="ignore")
```
```
54 changes: 33 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

👨‍💼 Python Wrapper for the Linkedin API

![v1.0.1](https://img.shields.io/badge/PyPI-v1.0.1-blue.svg)
[![PyPI version](https://badge.fury.io/py/linkedin-api.svg)](https://badge.fury.io/py/linkedin-api)

> No "official" API access required - just use a valid Linkedin account!
Programmatically send messages, perform searches, get profile data and more, all with a standard Linkedin account!
Programmatically send messages, perform searches, get profile data and more, all with a regular Linkedin user account!

##### USE AT YOUR OWN RISK 😉
This project should only be used as a learning project. Using it would violate Linkedin's User Agreement. I am not responsible for your account being blocked (which they will definitely do - see User Agreement section 8.2). Hint: **don't use a Linkedin account that you care about**)

##### Development Notice
This project is NOT STABLE by any stretch of the imagination. The API is subject to change at a moment's notice. @tomquirk will be amending the PyPI releases to reflect this fact soon.
This project should only be used as a learning project. Using it would violate Linkedin's User Agreement. I am not responsible for your account being blocked (which they will definitely do - see User Agreement section 8.2). Hint: **don't use a Linkedin account that you care about**)

## Overview

Expand All @@ -27,9 +25,11 @@ So specifically, this project aims to provide complete coverage for Voyager.
[How do we do it?](#in-depth-overview)

### Want to contribute?
[How do I find endpoints?](to-find-endpoints)

[Learn how to find endpoints](#to-find-endpoints)

## Installation

```
$ pip install linkedin-api
```
Expand All @@ -53,25 +53,26 @@ connections = api.get_profile_connections('1234asc12304', max_connections=200)
```

## Documentation

For a complete reference documentation, see the [DOCS.md](https://github.com/tomquirk/linkedin-api/blob/master/DOCS.md)

## Development Setup

### Dependencies

* Python 3.7
* A valid Linkedin user account (don't use your personal account, if possible)
* Pipenv (optional)
- Python 3.7
- A valid Linkedin user account (don't use your personal account, if possible)
- Pipenv (optional)

### Installation

1. Create a `.env` config file. An example is provided in `.env.example` - you include at least all of the settings set there.
2. Using pipenv...

```
$ pipenv install
$ pipenv shell
```
```
$ pipenv install
$ pipenv shell
```

### Running tests

Expand All @@ -86,22 +87,28 @@ $ python -m pytest tests
Linkedin will throw you a curve ball in the form of a Challenge URL. We currently don't handle this, and so you're kinda screwed. We think it could be only IP-based (i.e. logging in from different location). Your best chance at resolution is to log out and log back in on your browser.

##### Known reasons for Challenge:

- 2FA
- Rate-limit - "It looks like you’re visiting a very high number of pages on LinkedIn.". Note - n=1 experiment where this page was hit after ~900 contiguous requests in a single session (within the hour) (these included random delays between each request), as well as a bunch of testing, so who knows the actual limit.

Please add more as you come across them.

#### Search woes

- Mileage may vary when searching general keywords like "software" using the standard `search` method. They've recently added some smarts around search whereby they group results by people, company, jobs etc. if the query is general enough. Try to use an entity-specific search method (i.e. search_people) where possible.

<a name="in-depth-overview"></a>

## In-depth overview

Voyager endpoints look like this:

```
https://www.linkedin.com/voyager/api/identity/profileView/tom-quirk
```

Or, more clearly

```
___________________________________ _______________________________
| base path | resource |
Expand All @@ -112,19 +119,22 @@ They are authenticated with a simple cookie, which we send with every request, a

To get a cookie, we POST a given username and password (of a valid Linkedin user account) to `https://www.linkedin.com/uas/authenticate`.

<a name="to-find-endpoints"></a>

### To find endpoints...

We're looking at the Linkedin website and we spot some data we want. What now?

The most reliable method to find the relevant endpoint is to:
The most reliable method to find the relevant endpoint is to:

1. `view source`
2. `command-f`/search the page for some keyword in the data. This will exist inside of a `<code>` tag.
3. Scroll down to the **next adjacent element** which will be another `<code>` tag, probably with an `id` that looks something like
```html
<code style="display: none" id="datalet-bpr-guid-3900675">
{"request":"/voyager/api/identity/profiles/tom-quirk/profileView","status":200,"body":"bpr-guid-3900675"}
</code>
```
```html
<code style="display: none" id="datalet-bpr-guid-3900675">
{"request":"/voyager/api/identity/profiles/tom-quirk/profileView","status":200,"body":"bpr-guid-3900675"}
</code>
```
4. The value of `request` is the url! :woot:

You can also use the `network` tab in you browsers developer tools, but you will encounter mixed results.
Expand All @@ -140,20 +150,22 @@ Here's an example of making a request for an organisation's `name` and `groups`
```
The "querying" happens in the `decoration` parameter, which looks like
```
(
name,
groups*~(entityUrn,largeLogo,groupName,memberCount,websiteUrl,url)
)
```
So here, we request an organisation name, and a list of groups, where for each group we want `largeLogo`, `groupName`, etc.
Different endpoints use different parameters (and perhaps even different syntaxes) to specify these queries. Notice that the above query had a parameter `q` whose value was `universalName`; the query was then specified with the `decoration` parameter.
Different endpoints use different parameters (and perhaps even different syntaxes) to specify these queries. Notice that the above query had a parameter `q` whose value was `universalName`; the query was then specified with the `decoration` parameter.
In contrast, the `/search/cluster` endpoint uses `q=guided`, and specifies its query with the `guided` parameter, whose value is something like
```
List(v->PEOPLE)
```
It could be possible to document (and implement a nice interface for) this query language - as we add more endpoints to this project, I'm sure it will become more clear if such a thing would be possible (and if it's worth it).
2 changes: 1 addition & 1 deletion linkedin_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .linkedin import Linkedin

__title__ = "linkedin_api"
__version__ = "1.0.1"
__version__ = "1.1.0"
__description__ = "Python Wrapper for the Linkedin API"

__license__ = "MIT"
Expand Down
26 changes: 22 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import setuptools
import ast
import re
from pathlib import Path

CURRENT_DIR = Path(__file__).parent


def get_long_description() -> str:
readme_md = CURRENT_DIR / "README.md"
with open(readme_md, encoding="utf8") as ld_file:
return ld_file.read()


def get_version() -> str:
black_py = CURRENT_DIR / "linkedin_api/__init__.py"
_version_re = re.compile(r"__version__\s+=\s+(?P<version>.*)")
with open(black_py, "r", encoding="utf8") as f:
match = _version_re.search(f.read())
version = match.group("version") if match is not None else '"unknown"'
return str(ast.literal_eval(version))

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="linkedin_api",
version="1.0.1",
version=get_version(),
author="Tom Quirk",
author_email="[email protected]",
description="Python wrapper for the Linkedin API",
long_description=long_description,
long_description=get_long_description(),
long_description_content_type="text/markdown",
url="https://github.com/tomquirk/linkedin-api",
license="MIT",
Expand Down
1 change: 1 addition & 0 deletions tests/test_linkedin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
TEST_PROFILE_ID = os.getenv("TEST_PROFILE_ID")
TEST_CONVERSATION_ID = os.getenv("TEST_CONVERSATION_ID")

print(TEST_LINKEDIN_USERNAME, TEST_LINKEDIN_PASSWORD)
if not (
TEST_LINKEDIN_USERNAME
and TEST_LINKEDIN_PASSWORD
Expand Down

0 comments on commit eac2f5e

Please sign in to comment.