В последнее время мои поиски работы продвигаются медленно, а заинтересованных компаний всего несколько.Глядя на часы в кругу друзей, которые каждый день публикуют «Я очень занят», я чувствую, что могу бросить свое резюме на крючок форум тогда, чтобы заинтересованные компании могли найти меня.А теперь нет форума Lagou, описания набора различных компаний похожи, трудно понять, кто является тем, что я хочу.
Поскольку раньше я разрабатывал инструменты автоматического тестирования, а ежедневных станций бесчисленное множество, почему бы не изучить автоматическую доставку резюме? Если вы читаете это, значит, у вас такие же трудности с поиском работы, как и у меня, надеюсь, приведенные здесь идеи и сценарии автоматизации помогут вам.
Еще одно нытье, прежде чем мы начнем, очень ключевым моментом японской станции является селектор CSS.Если вы не знакомы с ним, вы можете использовать функцию Firefox «копировать селектор css». Как показано на рисунке:
Параметры во многих местах нужно заполнять селекторами.Что касается того, почему они заполнены, то это наверное либо опыт, либо я тоже пользуюсь инструментами для их выбора. (Я знаю, что в Chrome тоже есть эта функция, но алгоритм Firefox лучше)
Не хотите видеть мой бред, проект вздесь
1. Потяните крючок
Pull Hook — это профессиональный веб-сайт по подбору персонала в Интернете. Судя по звонкам, которые я получил от прямых сотрудников Boss, у них слабая защита от конкурентов, сканирующих страницы, поэтому автодоставка должна работать.
Первым делом обязательно скачайте puppeteer, запуститеyarn add puppeteer
, который потерпел неудачу, несмотря на мой синий свет.
Пришлось спасать страну криво, вручную качать Chrome Canary, а потом поДокументацияЗаявление, добавить предложение в .npmrcPUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
Отключить автоматическую загрузку Chromium. Если у вас есть хороший VPN или SS, вы сможете автоматически получать их напрямую.
После загрузки вы можете сначала попробовать Hello World:
const puppeteer = require('puppeteer');
const main = async () => {
const browser = await puppeteer.launch({
headless: false,
slowMo: 250,
executablePath: "C:\\Users\\Admin\\AppData\\Local\\Google\\Chrome SxS\\Application\\chrome.exe"
});
const page = await browser.newPage();
await page.goto('https://www.lagou.com/');
page.on('console', msg => console.log('PAGE LOG:', ...msg.args));
await page.evaluate(() => console.log(`url is ${location.href}`));
await browser.close();
};
main().catch(console.error);
Во-первых, у меня есть входная основная асинхронная функция call and catch, чтобы узел не жаловался, что промис не поймал ошибку.
В puppeteer.launch headless — запускать ли в безголовом режиме. Конечно, выбираем нет. Для удобства отладки slowMo — это интервал действия, а executablePath указывает на расположение скачанной мной Chrome Canary.
Код после этого должен открыть новую вкладку, просмотреть домашнюю страницу вкладки, консоль в настоящее время подключена и выйти, здесь нечего сказать.
Второй план — войти в свою учетную запись и просмотреть списки вакансий. Чтобы поделиться с вами кодом, не раскрывая вашего имени пользователя и пароля, вы должны использовать инструмент настройки, такой как dotenv, который должен быть знаком всем, кто развернул серверы узлов.
Поэтому я добавляю в первую строку файлаrequire('dotenv').config();
После этого, конечно же, перейдите непосредственно на страницу входа в пулл-хук, введите имя пользователя и пароль и нажмите «Войти».
await page.goto('https://passport.lagou.com/login/login.html');
// 用户名
await page.type('form.active > div:nth-child(1) > input:nth-child(1)', process.env.lagou_name);
// 密码
await page.type('form.active > div:nth-child(2) > input:nth-child(1)', process.env.lagou_pass);
// 登录按钮
await page.click('form.active > div:nth-child(5) > input:nth-child(1)');
await page.waitForNavigation();
// 直接跳转
await page.goto('https://www.lagou.com/zhaopin/webqianduan/?labelWords=label');
const title = await page.title();
console.log(title);
Здесь метод page.type является вводным текстом. а такжеprocess.env.lagou_name
Естественно, это происходит из конфигурации .env.
После нажатия кнопки входа страница перепрыгнет, поэтому используйтеpage.waitForNavigation()
дождаться входа в систему.
После успешного входа в систему определенно необходимо войти на соответствующую страницу, но нам не нужно имитировать нажатие на эти Node.js или веб-интерфейсы, потому что это обычные ссылки с тегом a. Мне просто нужно снова перейти на соответствующую страницу.
После входа в список вакансий я обычно выбираю город и сортирую его по времени обновления, в это время потянув за галочку обновит страницу, ссылка примерно такая:woohoo.lahoo.com/Jobs/list_me…
Кажется, что строка запроса должна быть заключена в кавычки, чтобы сделать переход параметризованным,const {escape} = require('querystring');
. Затем ссылка изменится на:
await page.goto(`https://www.lagou.com/jobs/list_${escape('web前端')}?px=new&city=${escape('天津')}#order`);
Третий шаг, безусловно, автоматическая доставка. Мы можем оглянуться вокруг, высказать свое мнение и решить, за какие компании голосовать, прежде чем действовать. Но такую программу написать не просто.Простой и грубый способ:
Получите 15 заданий на первой странице списка заданий, отфильтруйте задания и выберите первое из оставшихся заданий для доставки. После того, как доставка будет завершена, потянув галочку, вы автоматически отфильтруете доставленные вами, и вы можете повторить это.
так:
const jobs = await page.?eval('#s_position_list > ul > li', positionList =>
positionList.map(function mapPosition(position) {
const dataset = position.dataset;
const [salary1, salary2] = dataset.salary.split('-');
return {
title: dataset.positionname.toLowerCase(),
company: dataset.company.toLowerCase(),
salaryLo: parseInt(salary1),
salaryHi: parseInt(salary2),
id: parseInt(dataset.positionid)
};
})
);
page.$ — выполнить document.querySelector на странице, page.? — выполнить на странице document.querySelectorAll, эти два API также соответствуют eval, то есть может быть обратный вызов для фильтрации данных. Другими словами, page.?eval выполняет document.querySelectorAll и обрабатывает результат.
Эпоха jQuery, используемая внешним интерфейсом хука вытягивания, записывает данные в атрибут набора данных DOM, поэтому после получения списка DOM непосредственно извлеките соответствующие данные:
- positionname: название должности. Интерфейс кастинга должен фильтровать сеть Java.
- Компания: Название компании. Может использоваться для фильтрации недружественных и говорящих целей.
- зарплата: зарплата. Эти данные нужно немного обработать, чтобы потом облегчить фильтрацию.
- positionid: это часть ссылки на страницу резюме.
следовательно:
function getJobLink(jobs) {
const goodJobs = jobs.filter(function(job) {
if (job.title.indexOf('java') > -1) {
return false;
}
// 其它过滤条件
return true;
});
if (goodJobs.length > 0) {
const job = goodJobs[0];
return `https://www.lagou.com/jobs/${job.id}.html`;
}
return null;
}
Здесь у вас может быть своя зарплата, черный список компании и другие фильтры. После того, как мы закончили фильтрацию, берем оставшуюся первую работу и собираем ссылку на страницу доставки.
const jobLink = getJobLink(jobs);
//console.log(jobLink);
await page.goto(jobLink);
await page.click('.fr.btn_apply');
Получите публикуемую ссылку, перейдите к ссылке и нажмите «Опубликовать». . .
В это время будет как минимум две ситуации, одна из которых заключается в том, что хук в порядке, вы можете нажать «Я знаю». Другой - дернуть за крючок и сказать, что написанного мною многолетнего опыта недостаточно, чтобы подтвердить. Я хочу инвестировать в современный фронтенд и Node, и даже у основателя есть 8 лет опыта, поэтому, конечно, я должен игнорировать умственно отсталые требования 5-10 лет опыта.
await page.click('#delayConfirmDeliver').catch(() => {});
await page.click('#knowed').catch(() => {});
await page.waitForNavigation();
Здесь это означает, нажмите «Подтвердить доставку», если такой кнопки нет, не вешайте трубку. Нажмите «Я понял», если нет, не вешайте трубку. Наконец дождитесь обновления страницы.
В этот момент он может быть доставлен автоматически, просто нужно отполировать его, чтобы он мог быть автоматически доставлен до предела доставки тягового крюка.
2. Жилиан
Защита интерфейса ZhaOpin, особенно недавним хаосом на некоторую современную страницу в Интернете, люди чувствуют себя очень плохо, я обычно не использую.
Кажется, что на Zhilian много мошеннических компаний и обучающих компаний, поэтому делайте только автоматические обновления резюме, не отправляйте их.
Другими словами, этот Zhilian действительно волшебный веб-сайт.В некоторых местах для входа требуется код подтверждения, а в некоторых нет. . .
Итак, первый момент заключается в том, что вы должны зайти на страницу по ссылке поиска Baidu, поэтому проверочного кода нет, а адрес такой:А пока рекрутинг.com/jump/index_…
С точки зрения кода есть только один волшебный момент, то есть если всплывающее окно после входа в систему не закрывается, тег a на странице не может быть нажат.
await page.click('.Delivery_success_popdiv_title span.fr').catch(() => {});
await page.click('.amendBtn');
await page.waitForNavigation();
Итак, нажмите здесь на x, независимо от того, успешно это или нет, продолжайте нажимать, чтобы изменить резюме, и дождитесь перехода. Пожалуйста, обратитесь к файлу zhilian/index.js для кода предыдущего входа и последующего обновления точки.
3.100offer
100offer утверждает, что является рекрутинговым веб-сайтом, который «предлагает лучшим специалистам лучшие возможности».
Этот сайт не такой, как другие, когда вы нажимаете на город или следующую страницу, на его странице будет Ajax-запрос, а в возвращаемом результате будет только одно поле: html, а затем он вставит этот html в дерево DOM. с jQuery это потрясающе.
Поэтому о предыдущих лендингах и прыжках и говорить нечего:
const page = await browser.newPage();
await page.goto('https://cn.100offer.com/signin');
// 用户名
await page.type(
'#talent_email',
process.env.o100_name
);
// 密码
await page.type(
'#talent_password',
process.env.o100_pass
);
// 登录按钮
await page.click('#new_talent > div:nth-child(6) > input:nth-child(1)');
await page.waitForNavigation();
// 直接跳转
await page.goto('https://cn.100offer.com/job_positions');
// 帝都
await page.click('.locations.filters > div:nth-child(3)');
// 不要求学历
await page.click('.degree.filters > div:nth-child(7)');
Сейчас сложная ситуация, потому что 100 предложений нельзя отфильтровать по ключевым словам вакансий, поэтому на странице много нерелевантных вакансий Java.
Итак, вот в чем дело:
async function getJobLink() {
const jobs = await page.?eval('.position-list > .position-item a.h3-font', links =>
links.map(function mapLinks(link) {
return {
name: link.text.toLowerCase(),
url: link.href
};
})
);
const goodJobs = jobs.filter(function (job) {
if (job.name.indexOf('Node') > 0) {
return true;
}
if (job.name.indexOf('前端') > 0) {
return true;
}
return false;
});
if (goodJobs.length > 0) {
return goodJobs[0].url;
}
// 翻页
const nextEl = await page.$('a.next');
if (nextEl == null) {
return null;
}
await nextEl.click();
return await getJobLink();
}
getJobLink — это рекурсивная функция, которая рекурсивно ищет вакансии.
Во-первых, возьмите имена и ссылки на все должности на странице. Отфильтруйте по названию должности, например, мне нужны только Node и Frontend.
Если не нашел, то нужно перевернуть страницу. Сначала проверьте, есть ли элемент следующей страницыconst nextEl = await page.$('a.next');
Если вы не можете щелкнуть следующую страницу, поиск естественным образом завершится неудачно. Если есть, конечно, нажмите на следующую страницу, а затем выполните поиск рекурсивно.
После нажатия кнопки «доставить» игнорирование предупреждения похоже на «вытягивающий крючок», поэтому я больше не буду его публиковать. код в проекте100
в папке
4. Сообщество
Много раз я также искал какие-то возможности в сообществе, но я повторяю эти действия каждый день:
Откройте сообщество a, b, c, щелкните раздел набора, чтобы увидеть последние сообщения.
Почему бы не автоматизировать просмотр постов?
Я знаю только JS и Go, язык Go больше подходит для этой работы, а ноутбук Xiaomi, который я использую в командировках, имеет 2 ядра и 4 потока, было бы глупо не использовать его.
Во-первых, основная функция
func main() {
results := make(chan *Result)
var wg sync.WaitGroup
wg.Add(len(sites))
for _, site := range sites {
matcher, ok := matchers[site.resType]
if !ok {
matcher = matchers["default"]
}
go func(matcher Matcher, url string) {
err := doMatch(matcher, url, results)
if err != nil {
log.Println(err)
}
wg.Done()
}(matcher, site.url)
}
go func() {
wg.Wait()
close(results)
}()
display(results)
}
Если вы не знаете Go, вот ключевое слово go, ключевое слово chan и sync.WaitGroup поможет вам создавать новые темы и синхронизировать результаты.
Здесь у меня есть результаты синхронизации канала, а wg указывает на конец потока поиска сообщения. Затем я просматриваю ссылки сообщества, которые хочу посетить, и анализирую результаты, возвращаемые сообществом. Существует также поток, отвечающий за синхронизацию всех результатов и, наконец, вывод результатов в командную строку.
Существуют разные схемы разрешения для разных веб-сайтов. Итак, у него есть интерфейс matcher, который определяется следующим образом:
type Matcher interface {
match(reader io.Reader) ([]*Result, error)
}
Параметр, получаемый сопоставителем, — io.Reader, что примерно эквивалентно передаче любого параметра в JS или один из самых гибких способов его записи.
Для сообщества cnode, которое предоставляет спокойные интерфейсы, естественно анализировать json.
type CNodeTopic struct {
Title string `json:"title"`
CreateAt time.Time `json:"create_at"`
Content string `json:"content"`
}
type CNodeResp struct {
Success bool `json:"success"`
Data []CNodeTopic `json:"data"`
}
type CNodeJSON struct {}
Каждая тема cnode имеет несколько свойств, поэтому я просто выбираю те, которые мне нужны.
И далее разбор:
func (CNodeJSON) match(reader io.Reader) ([]*Result, error) {
resp, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
cnodeResp := CNodeResp{}
if err = json.Unmarshal(resp, &cnodeResp); err != nil {
return nil, err
}
if !cnodeResp.Success || cnodeResp.Data == nil {
return nil, fmt.Errorf("no response")
}
ret := make([]*Result, 0)
for _, topic := range cnodeResp.Data {
if time.Since(topic.CreateAt).Nanoseconds() - time.Hour.Nanoseconds() * 24 * dayLimit > 0 {
continue
}
ret = append(ret, &Result{title: topic.Title, email: emailRe.FindString(topic.Content), content:topic.Content})
}
return ret, nil
}
Всевозможные ежедневные разборы Golang, возврат если есть ошибка. Если все нормально, то естественно судить о времени постинга. Новые сообщения добавляются в результаты, нечего сказать.
Но большинство сайтов не так удобны, как cnode и должны парсить html.
так что я беру своеstudygolang.com举个栗子。首先必须引用 goquery,它是一个类似 jQuery 或者说是更像 Node 里面的 cheerio 的工具,不用这个的话就要自己递归搜索 html 节点了。 . .go get "github.com/PuerkitoBio/goquery"
И далее разбор:
type StudyGolangHTML struct {}
func (StudyGolangHTML) match(reader io.Reader) ([]*Result, error) {
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
return nil, err
}
ret := make([]*Result, 0)
doc.Find(".topic").Each(func(i int, selection *goquery.Selection) {
abbr := selection.Find("abbr")
timeStr, _ := abbr.Attr("title")
t, err := time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
return
}
if time.Since(t).Nanoseconds() - time.Hour.Nanoseconds() * 24 * dayLimit > 0 {
return
}
link := selection.Find(".title a")
ret = append(ret, &Result{title: link.Text(), email: "", content:link.AttrOr("href", "")})
})
return ret, nil
}
studygolang.comУзел каждой темы можно выбрать с помощью .topic, время указано в теге abbr, а заголовок и ссылка — под .title a.
Если у вас есть веб-сайт, который вы хотите найти, например, rust-china, kotlin-china и т. д., обычно это парсинг json или html, поэтому адаптироваться не составит труда.
Эпилог
Вы действительно читали это, я желаю вам найти подходящую работу как можно скорее.