Merge branch 'v3' into v3

This commit is contained in:
Federico 2024-09-02 14:27:29 +02:00 committed by GitHub
commit 020d7a24dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 242 additions and 10 deletions

View File

@ -515,19 +515,83 @@ Using this folder as a guide can be particularly helpful for:
python main.py --resume /path/to/your/resume.pdf
```
## Documentation
TODO ):
### Troubleshooting Common Issues
#### 1. OpenAI API Rate Limit Errors
**Error Message:**
openai.RateLimitError: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}}
**Solution:**
- Check your OpenAI API billing settings at https://platform.openai.com/account/billing
- Ensure you have added a valid payment method to your OpenAI account
- Note that ChatGPT Plus subscription is different from API access
- If you've recently added funds or upgraded, wait 12-24 hours for changes to take effect
- Free tier has a 3 RPM limit; spend at least $5 on API usage to increase
#### 2. LinkedIn Easy Apply Button Not Found
**Error Message:**
Exception: No clickable 'Easy Apply' button found
**Solution:**
- Ensure that you're logged into LinkedIn properly
- Check if the job listings you're targeting actually have the "Easy Apply" option
- Verify that your search parameters in the `config.yaml` file are correct and returning jobs with the "Easy Apply" button
- Try increasing the wait time for page loading in the script to ensure all elements are loaded before searching for the button
#### 3. Incorrect Information in Job Applications
**Issue:** Bot provides inaccurate data for experience, CTC, and notice period
**Solution:**
- Update prompts for professional experience specificity
- Add fields in `config.yaml` for current CTC, expected CTC, and notice period
- Modify bot logic to use these new config fields
#### 4. YAML Configuration Errors
**Error Message:**
yaml.scanner.ScannerError: while scanning a simple key
**Solution:**
- Copy example `config.yaml` and modify gradually
- Ensure proper YAML indentation and spacing
- Use a YAML validator tool
- Avoid unnecessary special characters or quotes
#### 5. Bot Logs In But Doesn't Apply to Jobs
**Issue:** Bot searches for jobs but continues scrolling without applying
**Solution:**
- Check for security checks or CAPTCHAs
- Verify `config.yaml` job search parameters
- Ensure your LinkedIn profile meets job requirements
- Review console output for error messages
### General Troubleshooting Tips
- Use the latest version of the script
- Verify all dependencies are installed and updated
- Check internet connection stability
- Use VPNs cautiously to avoid triggering LinkedIn security
- Clear browser cache and cookies if issues persist
For further assistance, please create an issue on the [GitHub repository](https://github.com/feder-cr/LinkedIn_AIHawk_automatic_job_application/issues) with detailed information about your problem, including error messages and your configuration (with sensitive information removed).
### Additional Resources
- [Video Tutorial: How to set up LinkedIn_AIHawk](https://youtu.be/gdW9wogHEUM)
- [OpenAI API Documentation](https://platform.openai.com/docs/)
- [LinkedIn Developer Documentation](https://developer.linkedin.com/)
- [Lang Chain Developer Documentation](https://python.langchain.com/v0.2/docs/integrations/components/)
## Troubleshooting
- **Carefully read logs and output :** Most of the errors are verbosely reflected just watch the output and try to find the root couse.
- **If nothing works by unknown reason:** Use tested OS. Reboot and/or update OS. Use new clean venv. Try update Python to the tested version.
- **ChromeDriver Issues:** Ensure ChromeDriver is compatible with your installed Chrome version.
- **Missing Files:** Verify that all necessary files are present in the data folder.
- **Invalid YAML:** Check your YAML files for syntax errors . Try to use external YAML validators e.g. https://www.yamllint.com/
- **OpenAI endpoint isues**: Try to check possible limits\blocking at their side
If you encounter any issues, you can open an issue on [GitHub](https://github.com/feder-cr/linkedIn_auto_jobs_applier_with_AI/issues).
Please add valuable details to the subject and to the description. If you need new feature then please reflect this.
I'll be more than happy to assist you!

Binary file not shown.

168
src/linkedin-api.py Normal file
View File

@ -0,0 +1,168 @@
from typing import Dict, List
from linkedin_api import Linkedin
from typing import Optional, Union, Literal
from urllib.parse import urlencode
class LinkedInEvolvedAPI(Linkedin):
def __init__(self, username, password):
super().__init__(username, password)
def search_jobs(
self,
keywords: Optional[str] = None,
companies: Optional[List[str]] = None,
experience: Optional[
List[
Union[
Literal["1"],
Literal["2"],
Literal["3"],
Literal["4"],
Literal["5"],
Literal["6"],
]
]
] = None,
job_type: Optional[
List[
Union[
Literal["F"],
Literal["C"],
Literal["P"],
Literal["T"],
Literal["I"],
Literal["V"],
Literal["O"],
]
]
] = None,
job_title: Optional[List[str]] = None,
industries: Optional[List[str]] = None,
location_name: Optional[str] = None,
remote: Optional[List[Union[Literal["1"], Literal["2"], Literal["3"]]]] = None,
listed_at=24 * 60 * 60,
distance: Optional[int] = None,
easy_apply: Optional[bool] = True,
limit=-1,
offset=0,
**kwargs,
) -> List[Dict]:
"""Perform a LinkedIn search for jobs.
:param keywords: Search keywords (str)
:type keywords: str, optional
:param companies: A list of company URN IDs (str)
:type companies: list, optional
:param experience: A list of experience levels, one or many of "1", "2", "3", "4", "5" and "6" (internship, entry level, associate, mid-senior level, director and executive, respectively)
:type experience: list, optional
:param job_type: A list of job types , one or many of "F", "C", "P", "T", "I", "V", "O" (full-time, contract, part-time, temporary, internship, volunteer and "other", respectively)
:type job_type: list, optional
:param job_title: A list of title URN IDs (str)
:type job_title: list, optional
:param industries: A list of industry URN IDs (str)
:type industries: list, optional
:param location_name: Name of the location to search within. Example: "Kyiv City, Ukraine"
:type location_name: str, optional
:param remote: Filter for remote jobs, onsite or hybrid. onsite:"1", remote:"2", hybrid:"3"
:type remote: list, optional
:param listed_at: maximum number of seconds passed since job posting. 86400 will filter job postings posted in last 24 hours.
:type listed_at: int/str, optional. Default value is equal to 24 hours.
:param distance: maximum distance from location in miles
:type distance: int/str, optional. If not specified, None or 0, the default value of 25 miles applied.
:param easy_apply: filter for jobs that are easy to apply to
:type easy_apply: bool, optional. Default value is True.
:param limit: maximum number of results obtained from API queries. -1 means maximum which is defined by constants and is equal to 1000 now.
:type limit: int, optional, default -1
:param offset: indicates how many search results shall be skipped
:type offset: int, optional
:return: List of jobs
:rtype: list
"""
count = Linkedin._MAX_SEARCH_COUNT
if limit is None:
limit = -1
query: Dict[str, Union[str, Dict[str, str]]] = {
"origin": "JOB_SEARCH_PAGE_QUERY_EXPANSION"
}
if keywords:
query["keywords"] = "KEYWORD_PLACEHOLDER"
if location_name:
query["locationFallback"] = "LOCATION_PLACEHOLDER"
query["selectedFilters"] = {}
if companies:
query["selectedFilters"]["company"] = f"List({','.join(companies)})"
if experience:
query["selectedFilters"]["experience"] = f"List({','.join(experience)})"
if job_type:
query["selectedFilters"]["jobType"] = f"List({','.join(job_type)})"
if job_title:
query["selectedFilters"]["title"] = f"List({','.join(job_title)})"
if industries:
query["selectedFilters"]["industry"] = f"List({','.join(industries)})"
if distance:
query["selectedFilters"]["distance"] = f"List({distance})"
if remote:
query["selectedFilters"]["workplaceType"] = f"List({','.join(remote)})"
if easy_apply:
query["selectedFilters"]["easyApply"] = "List(true)"
query["selectedFilters"]["timePostedRange"] = f"List(r{listed_at})"
query["spellCorrectionEnabled"] = "true"
query_string = (
str(query)
.replace(" ", "")
.replace("'", "")
.replace("KEYWORD_PLACEHOLDER", keywords or "")
.replace("LOCATION_PLACEHOLDER", location_name or "")
.replace("{", "(")
.replace("}", ")")
)
results = []
while True:
if limit > -1 and limit - len(results) < count:
count = limit - len(results)
default_params = {
"decorationId": "com.linkedin.voyager.dash.deco.jobs.search.JobSearchCardsCollection-174",
"count": count,
"q": "jobSearch",
"query": query_string,
"start": len(results) + offset,
}
res = self._fetch(
f"/voyagerJobsDashJobCards?{urlencode(default_params, safe='(),:')}",
headers={"accept": "application/vnd.linkedin.normalized+json+2.1"},
)
data = res.json()
elements = data.get("included", [])
new_data = []
for e in elements:
trackingUrn = e.get("trackingUrn")
if trackingUrn:
trackingUrn = trackingUrn.split(":")[-1]
e["job_id"] = trackingUrn
if e.get("$type") == "com.linkedin.voyager.dash.jobs.JobPosting":
new_data.append(e)
if not new_data:
break
results.extend(new_data)
if (
(-1 < limit <= len(results))
or len(results) / count >= Linkedin._MAX_REPEATED_REQUESTS
) or len(elements) == 0:
break
self.logger.debug(f"results grew to {len(results)}")
return results