first commit
This commit is contained in:
commit
3784b6f83e
|
@ -0,0 +1,47 @@
|
|||
# Игнорируем директорию с пользовательскими настройками IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Игнорируем файлы логов
|
||||
*.log
|
||||
|
||||
# Игнорируем кэш и временные файлы
|
||||
*.tmp
|
||||
*.swp
|
||||
*.bak
|
||||
|
||||
# Игнорируем файлы окружения Python
|
||||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
# Игнорируем конфигурации ОС
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Игнорируем директорию с зависимостями (например, для Node.js)
|
||||
node_modules/
|
||||
|
||||
# Игнорируем файлы виртуального окружения Python
|
||||
.venv/
|
||||
.env/
|
||||
test/
|
||||
|
||||
# Игнорируем скомпилированные файлы
|
||||
*.out
|
||||
*.class
|
||||
*.o
|
||||
*.log
|
||||
|
||||
# Игнорируем артефакты сборки
|
||||
/dist/
|
||||
/build/
|
||||
/target/
|
||||
|
||||
# Игнорируем файлы базы данных
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# Исключаем файл конфигурации среды, например, переменные окружения
|
||||
.env
|
||||
ww
|
||||
test.py
|
|
@ -0,0 +1,7 @@
|
|||
FROM python:3.10-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@ -0,0 +1,41 @@
|
|||
# linkedin получение данных профиля и вакансии!
|
||||
# linkedin ежедневный обмен вакансий в БД
|
||||
|
||||
|
||||
- [x] Профиль аккаунта по ссылке
|
||||
- [ ] Вакансия по ссылке (частично)
|
||||
- [ ] Ежедневный обмен вакансий (Ожидаю доступы)
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> Обмен по времени, один раз в сутки!
|
||||
> Пишет логи!
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> envtemp Пример .env для заполнения.
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
git clone https://git.xander.cx.ua/Xanders25/linkedin.git
|
||||
```
|
||||
|
||||
> Добавляем .env (Создаём через nano в файле envtemp посмотреть какие переменные нужны )
|
||||
|
||||
|
||||
|
||||
### Работа с образами:
|
||||
1. Сборка образа:
|
||||
```bash
|
||||
docker build -t linkedin .
|
||||
```
|
||||
|
||||
2. Запуск контейнера:
|
||||
```bash
|
||||
docker run -d --restart always --env-file .env \
|
||||
-v /home/logs:/app/logs \
|
||||
linkedin -p 8000:8000
|
||||
```
|
||||
3. Переходим по адресу http://IP:8000
|
|
@ -0,0 +1,188 @@
|
|||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from linkedin_api import Linkedin
|
||||
import logging
|
||||
from utils.logging_setup import configure_global_logging
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
|
||||
# # Ваши учетные данные LinkedIn
|
||||
username = os.getenv('USERNAME')
|
||||
password = os.getenv('PASSWD')
|
||||
|
||||
configure_global_logging()
|
||||
|
||||
app = FastAPI()
|
||||
api = Linkedin(username, password)
|
||||
|
||||
class Profile(BaseModel):
|
||||
name: str
|
||||
title: str
|
||||
url: HttpUrl
|
||||
|
||||
class UrlModel(BaseModel):
|
||||
link_profile: HttpUrl # Используем HttpUrl для проверки валидности URL
|
||||
|
||||
@app.post("/link_profile")
|
||||
async def profile_url(data: UrlModel):
|
||||
# Проверяем, что ссылка начинается с "https://xander"
|
||||
if not str(data.link_profile).startswith("https://www.linkedin.com/"):
|
||||
# Возвращаем исключение с кодом 400 и сообщением об ошибке
|
||||
raise HTTPException(status_code=400, detail="Неправильная ссылка")
|
||||
url_profiles = str(data.link_profile)
|
||||
path = urlparse(url_profiles).path
|
||||
print(path)
|
||||
|
||||
# Пропускаем '/in/' и получаем последнюю часть
|
||||
profiles = path.split("/")[2]
|
||||
|
||||
print(profiles)
|
||||
|
||||
profile = api.get_profile(profiles)
|
||||
logging.info(f"{profile}")
|
||||
firstName = profile['firstName']
|
||||
lastName = profile['lastName']
|
||||
locationName = profile['locationName']
|
||||
geoLocationName = profile['geoLocationName']
|
||||
headline = profile['headline']
|
||||
educations = profile['education']
|
||||
education = [
|
||||
{
|
||||
"schoolName": item.get("schoolName"),
|
||||
"Field_of_Study": item.get("fieldOfStudy"),
|
||||
"Degree": item.get('degreeName'),
|
||||
"Grade": "??",
|
||||
"Start Date": item.get("timePeriod", {}).get("startDate", {}).get("year"),
|
||||
"End Date": item.get("timePeriod", {}).get("endDate", {}).get("year"),
|
||||
"Currently study here": '??',
|
||||
"Description": "??"
|
||||
|
||||
|
||||
}
|
||||
for item in educations
|
||||
]
|
||||
|
||||
experiences = profile['experience']
|
||||
experience = [
|
||||
{
|
||||
"companyName": item.get("companyName"),
|
||||
"title": item.get("title"),
|
||||
"Employment type": "??",
|
||||
"Location": item.get('geoLocationName'),
|
||||
"Location Type": "??",
|
||||
"Start Date": {
|
||||
"month": item.get("timePeriod", {}).get("startDate", {}).get("month"),
|
||||
"year": item.get("timePeriod", {}).get("startDate", {}).get("year"),
|
||||
},
|
||||
"End Date":{
|
||||
"month": item.get("timePeriod", {}).get("endDate", {}).get("month"),
|
||||
"year": item.get("timePeriod", {}).get("endDate", {}).get("year"),
|
||||
},
|
||||
"Currently study here": '??',
|
||||
"Description": item.get('description')
|
||||
|
||||
|
||||
}
|
||||
for item in experiences
|
||||
]
|
||||
|
||||
volunteers = profile['volunteer']
|
||||
volunteer = [
|
||||
{
|
||||
"Organization": item.get("companyName"),
|
||||
"Role": item.get("role"),
|
||||
"Cause": item.get("cause"),
|
||||
"Location": item.get('geoLocationName'),
|
||||
"Start Date": {
|
||||
"month": item.get("timePeriod", {}).get("startDate", {}).get("month"),
|
||||
"year": item.get("timePeriod", {}).get("startDate", {}).get("year"),
|
||||
},
|
||||
"End Date":{
|
||||
"month": item.get("timePeriod", {}).get("endDate", {}).get("month"),
|
||||
"year": item.get("timePeriod", {}).get("endDate", {}).get("year"),
|
||||
},
|
||||
"Currently study here": '??',
|
||||
"Description": item.get('description')
|
||||
|
||||
|
||||
}
|
||||
for item in volunteers
|
||||
]
|
||||
all_skills = profile['skills']
|
||||
skills = [item['name'] for item in all_skills]
|
||||
all_languages = profile['languages']
|
||||
languages = [item['name'] for item in all_languages]
|
||||
|
||||
|
||||
|
||||
|
||||
# Если профиль не в виде словаря, возвращаем его напрямую
|
||||
return {"FirstName": firstName,
|
||||
"LastName":lastName,
|
||||
"Location": f"{geoLocationName} {locationName}",
|
||||
"Statement":headline,
|
||||
'Education - list': education,
|
||||
'Experience - list': experience,
|
||||
"Volunteering - list": volunteer,
|
||||
|
||||
|
||||
|
||||
"skills": skills,
|
||||
"languages":languages}
|
||||
|
||||
|
||||
|
||||
@app.post("/link_vacancy")
|
||||
async def vacancy_url(data: UrlModel):
|
||||
if not str(data.link_profile).startswith("https://www.linkedin.com/"):
|
||||
# Возвращаем исключение с кодом 400 и сообщением об ошибке
|
||||
raise HTTPException(status_code=400, detail="Неправильная ссылка")
|
||||
|
||||
query_params = parse_qs(urlparse(str(data.link_profile)).query)
|
||||
current_job_id = query_params.get("currentJobId", [None])[0]
|
||||
|
||||
jobs = api.get_job(current_job_id)
|
||||
location = jobs['formattedLocation']
|
||||
title = jobs['title']
|
||||
listedAt = jobs['listedAt']
|
||||
|
||||
# Преобразование из миллисекунд в секунды и конвертация
|
||||
future_date = datetime.fromtimestamp(listedAt / 1000)
|
||||
|
||||
# Текущая дата
|
||||
current_date = datetime.now()
|
||||
|
||||
# Разница в днях
|
||||
difference = abs((future_date - current_date).days)
|
||||
|
||||
# print(f"Разница в днях: {difference}")
|
||||
|
||||
|
||||
jobPostingId = jobs['jobPostingId']
|
||||
workplaceTypesResolutionResults = jobs['workplaceTypesResolutionResults']
|
||||
|
||||
|
||||
|
||||
# Извлекаем все localizedName
|
||||
localized_names = [value['localizedName'] for value in workplaceTypesResolutionResults.values()]
|
||||
|
||||
print(localized_names)
|
||||
|
||||
# localized_name = workplaceTypesResolutionResults['urn:li:fs_workplaceType:2']['localizedName']
|
||||
link = f'https://www.linkedin.com/jobs/view/{current_job_id}/'
|
||||
|
||||
|
||||
return {"job_id": current_job_id,
|
||||
"job_title": title,
|
||||
"minimum_annual_salary": f"minimum_annual_salary",
|
||||
"salary_currency": 'salary_currency',
|
||||
'location_type': "location_type",
|
||||
'location': location,
|
||||
"job_level": "job_level",
|
||||
"job_type": localized_names,
|
||||
"days_posted": difference,
|
||||
"hourly_rate": "hourly_rate",
|
||||
"link": link}
|
|
@ -0,0 +1,21 @@
|
|||
annotated-types==0.7.0
|
||||
anyio==4.8.0
|
||||
beautifulsoup4==4.12.3
|
||||
certifi==2024.12.14
|
||||
charset-normalizer==3.4.1
|
||||
click==8.1.8
|
||||
fastapi==0.115.6
|
||||
h11==0.14.0
|
||||
idna==3.10
|
||||
linkedin-api @ git+https://github.com/tomquirk/linkedin-api.git@dacec3c9f03b4f1fbddb4f7dfdef57ea408e40aa
|
||||
lxml==5.3.0
|
||||
pydantic==2.10.5
|
||||
pydantic_core==2.27.2
|
||||
python-dotenv==1.0.1
|
||||
requests==2.32.3
|
||||
sniffio==1.3.1
|
||||
soupsieve==2.6
|
||||
starlette==0.41.3
|
||||
typing_extensions==4.12.2
|
||||
urllib3==2.3.0
|
||||
uvicorn==0.34.0
|
|
@ -0,0 +1,37 @@
|
|||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
# Глобальная настройка логирования
|
||||
def configure_global_logging(
|
||||
log_file="logs/app.log",
|
||||
level=logging.INFO,
|
||||
max_bytes=5_000_000,
|
||||
backup_count=3
|
||||
):
|
||||
log_file_path = Path(log_file)
|
||||
log_file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Основной логгер
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(level)
|
||||
|
||||
if not logger.hasHandlers():
|
||||
# Обработчик для файла
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file, maxBytes=max_bytes, backupCount=backup_count
|
||||
)
|
||||
file_handler.setLevel(level)
|
||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Обработчик для консоли
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(level)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
Loading…
Reference in New Issue