{"id":253,"date":"2026-06-24T19:36:15","date_gmt":"2026-06-24T22:36:15","guid":{"rendered":"https:\/\/tromineo.com.br\/blog\/?p=253"},"modified":"2026-06-24T19:36:15","modified_gmt":"2026-06-24T22:36:15","slug":"automatizei-busca-por-freelas-com-bash-scripting","status":"publish","type":"post","link":"https:\/\/tromineo.com.br\/blog\/index.php\/2026\/06\/24\/automatizei-busca-por-freelas-com-bash-scripting\/","title":{"rendered":"Automatizei busca por freelas com bash scripting"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Essa \u00e9 a hist\u00f3ria de um programador mediano que precisava automatizar a busca por freelas no terminal<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">H\u00e1 um tempo atr\u00e1s comecei a trabalhar com os famosos &#8220;freelas&#8221; e uma das plataformas que eu encontrei onde posso encontrar ofertas de projetos \u00e9 a <a href=\"https:\/\/99freelas.com.br\">99freelas<\/a>. \u00c9 uma plataforma simples e objetiva que junta o profissional com quem precisa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pra mim, o problema come\u00e7ou quando comecei a passar mais tempo no site da plataforma garimpando projetos ou iniciativas onde eu poderia atuar. As vezes o que a gente precisa \u00e9 apenas a informa\u00e7\u00e3o na hora. Seria legal se eu pudesse buscar projetos direto pelo terminal n\u00e9? J\u00e1 que eu estou sempre nele, uma ferramenta CLI que listasse os projetos viria a calhar. Decidi ent\u00e3o dedicar algum tempo pra abordar esse problema.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A 99freelas n\u00e3o tem uma api p\u00fablica (no momento em que este post est\u00e1 sendo escrito) ent\u00e3o uma alternativa seria web scrapping: acessar a p\u00e1gina, baixar o conte\u00fado, tratar as informa\u00e7\u00f5es e organiz\u00e1-las de uma forma otimizada e acess\u00edvel, pra que eu pudesse ter a informa\u00e7\u00e3o que seria relevante pra tomar a decis\u00e3o se vou pegar o projeto ou n\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c9 uma tarefa relativamente simples e, analisando minhas op\u00e7\u00f5es, escolhi por fazer via shell scripting e um pouco de PHP. Isso vem da minha pr\u00f3pria experi\u00eancia como desenvolvedor mas existem milhares de op\u00e7\u00f5es pra fazer o scrapping.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A divis\u00e3o de responsabilidades ficou: bash faz o fetch (\u00e9 o que ele faz melhor), PHP faz o parsing do HTML (tem `preg_match_all`, `html_entity_decode` e as fun\u00e7\u00f5es `mb_*` para UTF-8 nativamente).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">As decis\u00f5es de implementa\u00e7\u00e3o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**Verificar depend\u00eancias cedo.**<\/strong> O script checa `curl` e `php` no in\u00edcio com `command -v`. Parece \u00f3bvio, mas sem isso qualquer erro posterior vira uma mensagem gen\u00e9rica de &#8220;comando n\u00e3o encontrado&#8221; no meio da execu\u00e7\u00e3o que ningu\u00e9m v\u00ea.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**PHP como arquivo tempor\u00e1rio, n\u00e3o pipe.**<\/strong> Embutir 100 linhas de PHP como string de argumento \u00e9 ileg\u00edvel e dificulta o debug. Com um arquivo em `$TMPDIR`, d\u00e1 pra inspecionar o parser se algo der errado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**<\/strong><strong>`mktemp`<\/strong><strong> com o padr\u00e3o <\/strong><strong>`XXXXXX`<\/strong><strong>.**<\/strong> Esses seis X servem pra deixar o nome aleat\u00f3rio. Sem isso, dois processos simult\u00e2neos poderiam usar o mesmo nome de arquivo e pisariam um no output do outro. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**<\/strong><strong>`trap`<\/strong><strong> para limpeza.**<\/strong> Os arquivos tempor\u00e1rios s\u00e3o removidos via `trap &#8216;rm -f &#8230;&#8217; EXIT INT TERM`. O motivo de usar `trap` e n\u00e3o um `rm` no final do script: ele roda mesmo se o script for interrompido com Ctrl+C ou morrer por erro. Sem isso, cada execu\u00e7\u00e3o deixa lixo em `\/tmp`.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**Tr\u00eas valida\u00e7\u00f5es do curl, em sequ\u00eancia.**<\/strong> O curl pode falhar de algumas formas diferentes: erro de rede (exit code <span style=\"text-decoration: underline;\">diferente<\/span> de 0), resposta de erro do servidor (c\u00f3digo HTTP <span style=\"text-decoration: underline;\">diferente<\/span> 200), ou resposta vazia mesmo com HTTP 200. S\u00e3o casos distintos e cada um descarta a p\u00e1gina por raz\u00f5es diferentes. Validar s\u00f3 o exit code deixaria passar respostas 404 com HTML v\u00e1lido ent\u00e3o apliquei esse tratamento adicional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**<\/strong><strong>`&#8211;nome`<\/strong><strong> aplicado depois da ordena\u00e7\u00e3o.**<\/strong> O filtro por t\u00edtulo \u00e9 aplicado <em>*ap\u00f3s*<\/em> ordenar e <em>*antes*<\/em> de aplicar o limite. A ordem importa: se voc\u00ea pede os 10 com mais propostas que cont\u00eam &#8220;landing page&#8221;, o filtro precisa acontecer sobre o conjunto j\u00e1 ordenado, n\u00e3o antes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>**<\/strong><strong>`&#8211;nome`<\/strong><strong> sem palavra-chave.**<\/strong> O script aceita `.\/freelas.sh &#8211;nome &#8220;e-commerce&#8221;` sem um termo de busca principal. A URL vai com `q=` vazio, trazendo todos os projetos da categoria, e o filtro \u00e9 aplicado no parsing. \u00datil quando voc\u00ea n\u00e3o sabe qual tecnologia vai estar no projeto mas sabe o tipo do servi\u00e7o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>** Configura\u00e7\u00e3o das cores via<\/strong> <strong>ANSI direto, sem `tput`.**<\/strong> `tput` consulta o banco de dados de terminais e pode falhar ou dar output errado em ambientes n\u00e3o-interativos. Os c\u00f3digos ANSI (`\\033[0;36m`) funcionam em qualquer terminal que os suporte sem depend\u00eancia externa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>## Resultado<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;`bash<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">.\/freelas.sh wordpress 5 propostas<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">.\/freelas.sh nodejs 20 data:desc<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">.\/freelas.sh &#8211;nome &#8220;e-commerce&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">.\/freelas.sh php &#8211;nome &#8220;landing page&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;`<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Funciona em qualquer Linux com `curl` e `php`. Sem setup adicional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/Tromineo\/scripts\/blob\/9e82f3459dfeb5398c2db96e104ead9051ec3a8e\/freelas.sh\">https:\/\/github.com\/Tromineo\/scripts\/blob\/9e82f3459dfeb5398c2db96e104ead9051ec3a8e\/freelas.sh<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por enquanto, esse script est\u00e1 me atendendo, mas ele n\u00e3o &#8220;nasceu&#8221; assim. Esse produto atual \u00e9 o resultado de v\u00e1rias itera\u00e7\u00f5es e pequenas melhorias que eram descobertas conforme o uso da ferramenta no dia a dia. Algumas mudan\u00e7as surgiram de inc\u00f4modos reais e outras de uma vontade de tornar a aplica\u00e7\u00e3o mais robusta e objetiva. O resultado foi uma evolu\u00e7\u00e3o gradual at\u00e9 chegar em algo que me atendesse minimamente naquele problema que eu tinha.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Essa \u00e9 a hist\u00f3ria de um programador mediano que precisava automatizar a busca por freelas no terminal H\u00e1 um tempo atr\u00e1s comecei a trabalhar com os famosos &#8220;freelas&#8221; e uma das plataformas que eu encontrei onde posso encontrar ofertas de projetos \u00e9 a 99freelas. \u00c9 uma plataforma simples e objetiva que junta o profissional com [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33,32],"tags":[3,4],"class_list":["post-253","post","type-post","status-publish","format-standard","hentry","category-automatizacao","category-scripting","tag-development","tag-software"],"_links":{"self":[{"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/posts\/253","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=253"}],"version-history":[{"count":14,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/posts\/253\/revisions"}],"predecessor-version":[{"id":270,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/posts\/253\/revisions\/270"}],"wp:attachment":[{"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=253"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tromineo.com.br\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}