<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by David Alecrim on Medium]]></title>
        <description><![CDATA[Stories by David Alecrim on Medium]]></description>
        <link>https://medium.com/@davidalecrim1?source=rss-bc899f49119b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*PXodqSJeFLecqv8UXuBQ2g.jpeg</url>
            <title>Stories by David Alecrim on Medium</title>
            <link>https://medium.com/@davidalecrim1?source=rss-bc899f49119b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 10:10:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@davidalecrim1/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Can you spot what can go wrong with this Golang code?]]></title>
            <link>https://medium.com/@davidalecrim1/can-you-spot-what-can-go-wrong-with-this-golang-code-8ca0270fa568?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ca0270fa568</guid>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Sat, 13 Dec 2025 01:06:00 GMT</pubDate>
            <atom:updated>2025-12-13T01:13:56.666Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*57Y-EBhMqjAmaeftvaSO5A.png" /></figure><h3>Introduction</h3><p>Today I was working on a feature when a colleague asked me for help. He showed me a log that looked obvious at first: context canceled. One of those errors that usually points in a clear direction.</p><p>I gave a few debugging tips and went back to my task. A while later, he came back. The issue was still there.</p><p>The code was simple in theory. Two API calls ran concurrently, their results were combined, and a third API call used that data. The first two calls worked fine. The third one failed every time, logging context canceled.</p><p>That’s when it stopped making sense.</p><p>We checked the usual things: API logic, gRPC cancellations, timeouts, early returns. Nothing explained the behavior.</p><p>The problem wasn’t where we were looking.</p><p>Let’s walk through the scenario.</p><h3>The situation</h3><p>Imagine a function called DoSomething. It receives a context.Context, starts two goroutines using errgroup, waits for both of them to finish, and then calls a third API.</p><p>Conceptually, it looks like this: API 1 and API 2 run in parallel, return some data, and API 3 consumes that data.</p><p>Here is a simplified version of the code:</p><pre>func DoSomething(ctx context.Context) error {<br>    // some fancy logic<br>    // ...<br><br>    // let&#39;s make some concurrent API calls<br>    g, ctx := errgroup.WithContext(ctx)<br><br>    var r1, r2 string<br>    g.Go(func() error {<br>        resp, err := CallAPI1(ctx)<br>        if err != nil {<br>            return err<br>        }<br>        r1 = resp<br>        return nil<br>    })<br>    g.Go(func() error {<br>        resp, err := CallAPI2(ctx)<br>        if err != nil {<br>            return err<br>        }<br>        r2 = resp<br>        return nil<br>    })<br>    if err := g.Wait(); err != nil {<br>        slog.Error(&quot;oh not something happened&quot;, &quot;err&quot;, err)<br>        return err<br>    }<br><br>    // now let&#39;s use the data to make a third API call<br>    if _, err := CallAPI3(ctx, r1, r2); err != nil {<br>        slog.Error(&quot;api3 call failed&quot;, &quot;err&quot;, err)<br>        return err<br>    }<br>    return nil<br>}</pre><p>When this runs, API 1 and API 2 behave as expected. But API 3 fails and logs something like:</p><pre>api3 call failed: context canceled</pre><p>At this point, the natural reaction is to blame the context. But which cancellation are we actually dealing with?</p><h3>What errgroup.WithContext really does</h3><p>The library errgroup is a Go abstraction built on top of sync.WaitGroup that adds error propagation and context cancellation to concurrent workflows. With a WaitGroup, you can wait for multiple goroutines to finish, but you have to manage errors and cancellation manually. errgroup, on the other hand, lets each goroutine return an error, and the first non-nil error automatically causes Wait() to return that error and cancels a derived context shared by the group. In practice, this makes errgroup a better fit when goroutines are part of the same logical operation and should stop together on failure, while WaitGroup is more appropriate when you only care about synchronization and lifecycle, not shared failure semantics.</p><p>errgroup.WithContext does two things. It creates a group that waits for multiple goroutines, and it also returns a derived context. That derived context is canceled as soon as any goroutine in the group returns a non-nil error.</p><p>This is a powerful abstraction. It lets all goroutines stop early when one of them fails. But it also introduces a new context into the function, one that has very specific cancellation semantics.</p><p>And this is where the problem starts to take shape.</p><p>Notice this line again:</p><pre><br>g, ctx := errgroup.WithContext(ctx)</pre><p>At a glance, it looks harmless. But now the function has lost something important: there is no longer a clear distinction between the context that was passed into DoSomething and the context controlled by the errgroup.</p><p>The third API call is no longer using the original context. It’s using the errgroup context.</p><h3>The subtle mistake</h3><p>The issue here is not concurrency. It’s not the APIs. It’s not even errgroup itself.</p><p>The issue is variable shadowing. The classic shadowing mistake that was not spotted by a heavy golangci-lint pipeline.</p><p>By reusing the name ctx, the original context passed into DoSomething is replaced by the context returned by errgroup.WithContext. From that point on, every use of ctx refers to the group-controlled context.</p><p>When one of the goroutines returns an error, g.Wait() returns that error and, as part of its contract, cancels the errgroup context. That cancellation is correct and expected. But why was this returning a context cancelled if the API 1 and API 2 were working as expected?</p><p>That happened because the g.Wait() cancells the context after it is done. Meaning the context is always cancelled when the concurrent operation is over.</p><p>So the failure is not mysterious at all. API 3 is doing exactly what it should: it refuses to make a network request with a canceled context.</p><h3>Why this is easy to miss in Go</h3><p>Go makes shadowing easy to use — and easy to misuse — because it is a deliberate language feature, not an accident.</p><p>Shadowing happens when a variable declared in an inner scope has the same name as a variable in an outer scope. From that point on, the inner variable hides the outer one until the scope ends. Go allows this, and in many cases it’s idiomatic and even desirable.</p><p>The := operator is where this usually shows up. Depending on scope, it can introduce a new variable or reuse an existing one. A very common pattern looks like this:</p><pre>ctx := context.Background()<br><br>if needsTimeout {<br>    ctx, cancel := context.WithTimeout(ctx, time.Second)<br>    defer cancel()<br>    callSomething(ctx)<br>}<br>callSomethingElse(ctx)</pre><p>At first glance, this looks reasonable. Inside the if, we create a context with a timeout. Outside of it, we keep using ctx.</p><p>But that’s not what actually happens.</p><p>Inside the if, a new ctx is created. It shadows the outer one. When the block ends, that inner context is gone, and callSomethingElse receives the original context.Background(), not the timeout-bound context you might assume was propagated.</p><p>This is fine when you understand exactly how scope works. The danger is that the code reads as if ctx is being “updated”, when in reality it’s being replaced temporarily.</p><p>Now combine this behavior with something like errgroup.WithContext, where the returned context has very specific cancellation semantics. Reusing the same variable name makes it easy to forget that you’re no longer dealing with the original context at all.</p><p>Contexts are not just values. They encode ownership and lifetime. Shadowing them doesn’t just hide a variable — it hides a change in control flow. And that’s how bugs like “why is my context already canceled?” slip into otherwise clean-looking Go code.</p><h3>The fix and the lesson</h3><p>The fix is simple: give the errgroup context its own name.</p><pre>func DoSomething(ctx context.Context) error {<br>    // some fancy logic<br>    // ...<br><br>    // let&#39;s make some concurrent API calls with the correct context<br>    g, gctx := errgroup.WithContext(ctx)<br><br>    var r1, r2 string<br>    g.Go(func() error {<br>        resp, err := CallAPI1(gctx)<br>        if err != nil {<br>            return err<br>        }<br>        r1 = resp<br>        return nil<br>    })<br>    g.Go(func() error {<br>        resp, err := CallAPI2(gctx)<br>        if err != nil {<br>            return err<br>        }<br>        r2 = resp<br>        return nil<br>    })<br>    if err := g.Wait(); err != nil {<br>        slog.Error(&quot;oh not something happened&quot;, &quot;err&quot;, err)<br>        return err<br>    }<br><br>    // now let&#39;s use the data to make a third API call<br>    if _, err := CallAPI3(ctx, r1, r2); err != nil {<br>        slog.Error(&quot;api3 call failed&quot;, &quot;err&quot;, err)<br>        return err<br>    }<br>    return nil<br>}</pre><p>Now the code documents intent. Goroutines use gctx, and the developer is forced to make an explicit decision about which context to use after g.Wait().</p><p>More importantly, the lesson is not “never shadow variables.” It’s to be very deliberate when you do. Shadowing a simple value like an integer is rarely a problem. Shadowing something with behavior and lifetime, like a context, is where bugs quietly appear.</p><h3>Conclusion</h3><p>This bug wasn’t caused by concurrency being hard or Go being tricky. It was caused by a small naming decision that blurred an important boundary.</p><p>When reading or writing Go code, pay attention to where variables come from and what they represent over time. Especially with context.Context, the name you choose is part of the design.</p><p>Most of the time, Go does exactly what you tell it to do. The challenge is making sure you’re telling it the right thing.</p><p>See you on the next one! Bye!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ca0270fa568" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go is Passed By Value, Not Reference]]></title>
            <link>https://medium.com/@davidalecrim1/go-is-passed-by-value-not-reference-9206652fb3d0?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/9206652fb3d0</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Mon, 18 Aug 2025 10:22:27 GMT</pubDate>
            <atom:updated>2025-08-18T10:22:27.528Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m7KKgl3OBD3Q22tPvx0SWw.png" /></figure><p>The Go programming language is powerful and versatile. You can build anything from web applications to network services, cloud software, CLI tools, and even some systems programming tasks. The beauty of Go lies in its simplicity — but that doesn’t mean the language itself is simple. Its simplicity comes from carefully designed abstractions.</p><p>One important design choice in Go is <strong>how values are passed between functions</strong>. At first glance, it seems like you can pass things either <em>by value</em> or <em>by reference</em>. But in reality, Go passes <strong>everything by value</strong>.</p><p>That statement might feel confusing. After all, you can pass pointers in Go, and modifying data through them changes the original. So why does Go say <em>everything is passed by value</em>? Let’s break it down with examples.</p><h3>Passing Structs by Value</h3><p>When you pass a struct directly to a function, Go makes a copy of that struct. Any modification inside the function won’t affect the original value.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/47171028c66d3a98e59199de8c1f68d9/href">https://medium.com/media/47171028c66d3a98e59199de8c1f68d9/href</a></iframe><p>The output will be:</p><pre>Inside function: {Alice 26}<br>Outside function: {Alice 25}</pre><p>Here, the struct was passed <strong>by value</strong> — a copy was created.</p><h3>Passing Structs with Pointers</h3><p>If you need to modify the struct inside the function, you can pass a pointer to it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43daa0486729282f3b7662aeceb88d74/href">https://medium.com/media/43daa0486729282f3b7662aeceb88d74/href</a></iframe><p>The output will be:</p><pre>Inside function: {Alice 26}<br>Outside function: {Alice 26}</pre><p>Now the function updates the original value because we passed a pointer.</p><h3>Pointers Are Still Passed by Value</h3><p>When you pass a struct directly to a function, Go makes a copy of that struct. Any modification inside the function won’t affect the original value.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/24c0b82a869d69f7df8e84865b47d462/href">https://medium.com/media/24c0b82a869d69f7df8e84865b47d462/href</a></iframe><p>The output will be:</p><pre>Pointer value outside function (address of user): 0x1400012c000<br>Pointer variable address outside function: 0x14000104030<br>Pointer value inside function (address of user): 0x1400012c000<br>Pointer variable address inside function: 0x14000104040</pre><p>Notice that the memory addresses from the user are printed the same, but when it&#39;s printed the memory address of the pointer itself, it is different. That happens because the pointer was copied.</p><p>This would look like in the computer memory:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/927/1*HYlhM2HHCUHaP5AS94O8VQ.png" /></figure><h3>Maps and Slices Work the Same Way</h3><p>Go’s built-in data structures also follow this rule. Take maps, for example. When you pass a map to a function, the map header (a small struct containing pointers) is copied, but both copies still point to the same underlying data.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dbad0a4bcf2307ebd772ba3750723f57/href">https://medium.com/media/dbad0a4bcf2307ebd772ba3750723f57/href</a></iframe><p>The map is passed <strong>by value</strong>, but since the underlying data is shared, modifications are visible outside.</p><p>Slices behave similarly: the slice header (pointer, length, capacity) is copied, but the underlying array is shared.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/99ac1a1e69cb5d370dc7ca3560141fc0/href">https://medium.com/media/99ac1a1e69cb5d370dc7ca3560141fc0/href</a></iframe><h3>Conclusion</h3><p>Go always passes values <strong>by value</strong>. The trick is that sometimes what’s copied is a pointer, as in the case of maps, slices, and explicit pointer usage. This design choice keeps the language simple and predictable:</p><ul><li>Structs → copied fully when passed by value.</li><li>Pointers → the pointer itself is copied, but both point to the same memory.</li><li>Maps and slices → their headers are copied, but the underlying data is shared.</li></ul><p>This reflects Go’s philosophy: <strong>simplicity doesn’t mean easy; it means clear and consistent</strong>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9206652fb3d0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Race conditions, ou condições de corrida. Você ouviu falar?]]></title>
            <link>https://medium.com/@davidalecrim1/race-conditions-ou-condi%C3%A7%C3%B5es-de-corrida-voc%C3%AA-ouviu-falar-4cdc6b6b0abf?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/4cdc6b6b0abf</guid>
            <category><![CDATA[race-condition]]></category>
            <category><![CDATA[desenvolvimento]]></category>
            <category><![CDATA[banco-de-dados]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Sun, 22 Sep 2024 20:22:51 GMT</pubDate>
            <atom:updated>2024-09-22T20:22:51.024Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/680/1*TD0Gy-UP01cDDEasSxhCZQ.jpeg" /></figure><p>Todo desenvolvedor, em algum momento, precisará entender e saber as implicações das race conditions no desenvolvimento de aplicações, especialmente no backend ao interagir com bancos de dados.</p><p>Imagine sua conta bancária com saldo de R$ 100. Agora, você recebe um PIX no valor de R$ 200 e, no mesmo instante, realiza uma compra no débito de R$ 50. Se ambas as operações acessarem o saldo ao mesmo tempo, e a race condition não for tratada na aplicação e no banco de dados, o saldo poderia resultar em um valor incorreto. A primeira operação consulta o saldo de R$ 100 e, após o crédito, resulta em R$ 300, finalizando a operação. Já a segunda operação, ocorrendo simultaneamente, também lê o saldo de R$ 100 e, após o débito, resulta em R$ 50 de saldo definitivo na conta, desconsiderando o crédito anterior e causando uma inconsistência no saldo persistido no banco de dados.</p><p>As race conditions ocorrem quando múltiplas operações acessam e modificam dados simultaneamente, sem a devida sincronização. O resultado pode ser imprevisível, causando erros como dados inconsistentes no banco ou falhas no sistema.</p><p>Para resolver esse problema, existem duas abordagens comuns: a <strong>gestão otimista</strong> e a <strong>gestão pessimista</strong> de race conditions.</p><h3>📊 Gestão Pessimista</h3><p>Na gestão pessimista, a aplicação assume que as operações concorrentes causarão conflitos, então ele bloqueia o recurso compartilhado até que uma operação finalize seu uso. Esse tipo de controle garante que, enquanto uma transação está sendo processada, nenhuma outra pode acessar o recurso, eliminando a possibilidade de race conditions.</p><p>Exemplo em SQL:</p><pre>BEGIN;<br>SELECT saldo FROM contas WHERE id = 1 FOR UPDATE;<br>-- Atualiza o saldo com base na lógica de crédito ou débito<br>UPDATE contas SET saldo = saldo + 200 WHERE id = 1;<br>COMMIT;</pre><p>No exemplo acima, o comando FOR UPDATE garante que o saldo da conta está bloqueado para outras transações enquanto a operação atual está sendo executada, evitando que outra transação leia ou escreva no mesmo registro até que o COMMIT seja realizado.</p><h3>🚀 Gestão Otimista</h3><p>Na gestão otimista, a aplicação assume que conflitos são raros e permite que as operações concorram pelo recurso sem bloqueios explícitos. Quando uma operação é finalizada, a aplicação verifica se o dado foi modificado durante a transação. Caso tenha sido, a operação falha e a aplicação deve tentar novamente ou abortar.</p><p>A gestão otimista é mais eficiente em cenários onde o conflito de concorrência é raro, já que as transações podem ser executadas simultaneamente sem bloqueios. Contudo, ela exige um mecanismo de controle de versões ou verificação de mudanças para detectar conflitos.</p><p>Exemplo com controle de versão (usando uma coluna versao no banco):</p><pre>BEGIN;<br>SELECT saldo, versao FROM contas WHERE id = 1;<br>-- Verifica se a versão é a mesma antes de atualizar<br>UPDATE contas SET saldo = saldo + 200, versao = versao + 1 WHERE id = 1 AND versao = 1;<br>COMMIT;</pre><p>Aqui, a coluna versao é usada para garantir que a transação só será bem-sucedida se o registro não foi alterado por outra operação concorrente. Caso a versao tenha mudado, a transação falha e pode ser repetida ou abortada.</p><h3>Exemplo em Go com SQL otimista</h3><p>No Go, podemos implementar a estratégia de controle otimista ao usar uma função que tenta realizar a operação e lida com o erro caso haja um conflito:</p><pre>package main<br><br>import (<br>    &quot;database/sql&quot;<br>    &quot;fmt&quot;<br>    &quot;log&quot;<br>    _ &quot;github.com/lib/pq&quot;<br>)<br>func updateSaldo(db *sql.DB, id int, amount int) error {<br>    tx, err := db.Begin()<br>    if err != nil {<br>        return err<br>    }<br>    defer tx.Rollback()<br>    var saldo int<br>    var versao int<br>    err = tx.QueryRow(&quot;SELECT saldo, versao FROM contas WHERE id = $1&quot;, id).Scan(&amp;saldo, &amp;versao)<br>    if err != nil {<br>        return err<br>    }<br>    // Tenta atualizar o saldo e a versão<br>    res, err := tx.Exec(&quot;UPDATE contas SET saldo = $1, versao = versao + 1 WHERE id = $2 AND versao = $3&quot;, saldo+amount, id, versao)<br>    if err != nil {<br>        return err<br>    }<br>    rowsAffected, err := res.RowsAffected()<br>    if err != nil {<br>        return err<br>    }<br>    if rowsAffected == 0 {<br>        return fmt.Errorf(&quot;Conflito de versão, tente novamente&quot;)<br>    }<br>    return tx.Commit()<br>}<br>func main() {<br>    connStr := &quot;user=postgres dbname=teste sslmode=disable&quot;<br>    db, err := sql.Open(&quot;postgres&quot;, connStr)<br>    if err != nil {<br>        log.Fatal(err)<br>    }<br>    err = updateSaldo(db, 1, 200)<br>    if err != nil {<br>        fmt.Println(&quot;Erro ao atualizar saldo:&quot;, err)<br>    } else {<br>        fmt.Println(&quot;Saldo atualizado com sucesso&quot;)<br>    }<br>}</pre><p>No exemplo acima, utilizamos a abordagem otimista. Caso a versão do registro tenha sido modificada por outra transação, a aplicação retorna um erro, indicando que a operação precisa ser repetida.</p><h3>Conclusão</h3><p>Race conditions podem ser críticas ao desenvolver sistemas concorrentes e, para tratá-las, é essencial escolher a estratégia correta. A gestão pessimista funciona bem quando há uma alta probabilidade de conflitos, já que bloqueia o acesso ao recurso. Já a gestão otimista é mais eficiente quando os conflitos são raros, permitindo maior paralelismo nas operações.</p><p>Ao lidar com banco de dados e sistemas de alta concorrência, sempre considere a melhor abordagem para garantir a integridade e consistência dos dados.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4cdc6b6b0abf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[“Por baixo do capô” — Entendendo como funciona uma linguagem de programação]]></title>
            <link>https://medium.com/@davidalecrim1/por-baixo-do-cap%C3%B4-entendendo-como-funciona-uma-linguagem-de-programa%C3%A7%C3%A3o-f1ba0866f56e?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/f1ba0866f56e</guid>
            <category><![CDATA[linguagem-de-programação]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[software]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Sat, 24 Feb 2024 20:05:56 GMT</pubDate>
            <atom:updated>2024-02-24T20:05:56.317Z</atom:updated>
            <content:encoded><![CDATA[<h3>“Por baixo do capô” — Entendendo como funciona uma linguagem de programação</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9Ny1Js1fS0oZQb2b.png" /><figcaption>O logo da linguagem de programação do Python que usaremos como exemplo</figcaption></figure><p>Por trás de cada linha de código em um simples programa feito em alguma linguagem de programação há vasto mundo complexo que foi pensado em detalhe em como podemos criar coisas no mundo da computação, e nesse mundo de engenharia de software não há um certo ou errado, apenas trocas (i.e. trade-offs) de cada escolha. Seja uma linguagem de programação interpretada, compilada ou entre outras que podem combinar ambas abordagens.</p><p>Aqui vamos explorar um pouco mais sobre linguagem de programação interpretada, desvendando sua arquitetura interna e entendendo como ela difere das linguagens compiladas. Vamos utilizar Python como um exemplo, mas os conceitos são similares para outras linguagens interpretadas.</p><p><strong>Compilado vs Interpretado</strong></p><p>Uma linguagem de programação interpretada executa o código fonte linha por linha em tempo real, traduzindo e executando cada instrução conforme encontrada. Isso permite rápida prototipagem e facilita a depuração, mas pode resultar em um desempenho geralmente mais lento. Por outro lado, uma linguagem compilada, como por exemplo Go, traduz todo o código fonte em código de máquina antes da execução, produzindo um programa executável independente, especifico para aquela arquitetura de hardware e o sistema operacional em que será executado, então, por exemplo, um binário para Windows não irá rodar em um MacOS, e um binário para MacOS compilado para processador x86–64 da Intel, não funcionará para os MacOS maios novos com processador ARM64 da Apple. Já as linguagens interpretadas abstraem a complexidade com um interpretador, e esse será responsável por ser compatível com determinado hardware e sistema operacional, trazendo uma vantagem em portabilidade entre plataformas do código escrito.</p><p><strong>Linguagem Dinamicamente Tipada vs Estaticamente Tipada</strong></p><p>Outra distinção importante entre linguagens de programação é se elas são dinamicamente ou estaticamente tipadas. Em linguagens dinamicamente tipadas, como Python, os tipos das variáveis são associados aos valores em tempo de execução. Isso significa que você não precisa declarar explicitamente o tipo de uma variável ao criar ou atribuir um valor a ela. Por exemplo, em Python, você pode simplesmente escrever <strong>valor = 10</strong> sem especificar o tipo de <strong>valor</strong>, e o interpretador Python entenderá que <strong>valor</strong> é um inteiro(int). Por outro lado, em linguagens estaticamente tipadas, como Go ou C++, você precisa declarar o tipo de uma variável explicitamente antes de usar, como <strong>var valor int = 10</strong>. Isso geralmente é feito durante a compilação do código. Embora as linguagens dinamicamente tipadas ofereçam mais flexibilidade e facilidade de uso em alguns casos, elas tendem a introduzir bugs sutis no código que só serão percebidos durante a execução do programa. As linguagens estaticamente tipadas podem oferecer melhor desempenho e segurança, já que os erros de tipo são detectados em tempo de compilação em vez de em tempo de execução. Cada abordagem tem seus próprios prós e contras, lembre-se, não há certo ou errado, somente há trade-offs em engenharia de software. A escolha entre elas geralmente depende das necessidades específicas do projeto.</p><p><strong>Arquitetura Interna de Uma Linguagem Interpretada</strong></p><p>Para fins didáticos, vamos utilizar o Python como exemplo, dado que a estrutura pode variar levemente entre linguagens. Considere o código a seguir:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1fc8ae8d007aa55d162d18da89b4ed69/href">https://medium.com/media/1fc8ae8d007aa55d162d18da89b4ed69/href</a></iframe><p>Quando esse executado em um computador, o processo ocorre da seguinte forma:</p><ol><li><strong>Escrevendo o Código</strong>: O processo começa com a pessoa desenvolvedora escrevendo o código em um editor de texto, como por exemplo <strong>VS Code</strong>, e salvando-o como um arquivo com extensão .py.</li><li><strong>Interpretador do Python</strong>: O código é enviado para o interpretador, que é responsável por executar o programa, ele consiste em duas partes: <strong>o compilador</strong> e <strong>Máquina Virtual Python (PVM)</strong>. O compilador, que converte o código Python em <strong>byte code,</strong> conhecido pela extensão .pyc e a PVM executa esse byte code, seguindo as instruções uma por uma.</li><li><strong>Bibliotecas / Módulos</strong>: Se o código utilizar módulos de biblioteca do Python, como por exemplo import requests , esses também serão convertidos em byte code e executados pelo PVM.</li><li><strong>Do Byte Code para o Código de Máquina:</strong> O byte code é então convertido em código de máquina, que é entendido diretamente para o especifico processador (CPU) do computador, como por exemplo um ARM64 do MacOS. Após a conversão para código de máquina, o computador usa esse código para executar o programa.</li></ol><p>Este processo é repetido cada vez que o programa é executado. Ele pode ser resumido na imagem abaixo:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/817/1*GOwE6LwaKsAtwtBYiqXHZA.png" /><figcaption>Detalhamento do passo a passo acima em uma figura</figcaption></figure><p>É importante mencionar que estamos utilizando apenas um exemplo para fins didáticos, há vários pontos de complexidade que são abstraídos nesse processo, lembre-se que cada linguagem de programação tem seu “mundo”, assim como cada sistema operacional e por ai vai.</p><p><strong>Conclusão</strong></p><p>Por trás de cada linha de código, há um vasto mundo de decisões e compromissos que foram cuidadosamente considerados para permitir que os programadores desenvolvam soluções eficientes e robustas para uma ampla variedade de problemas.</p><p>A distinção entre linguagens compiladas e interpretadas, bem como entre linguagens dinamicamente e estaticamente tipadas, ressalta a diversidade de abordagens disponíveis para os desenvolvedores e as trocas inerentes a cada escolha. Não há uma solução única ou correta, mas sim uma série de trade-offs que devem ser ponderados de acordo com as necessidades específicas de cada projeto. Lembre-se de escolher bem conforme cada necessidade.</p><p>Por fim, espero que esse entendimento de como as linguagens de programação interpretadas funcionam possa ajudar em sua visão mais holística desse mundo complexo e maravilhoso de engenharia de software.</p><p>Até a próxima. Se chegou até aqui, deixe sua curtida.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f1ba0866f56e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[O Futuro dos Mecanismos de Busca na Internet]]></title>
            <link>https://medium.com/@davidalecrim1/o-futuro-dos-mecanismos-de-busca-na-internet-66fa710900d8?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/66fa710900d8</guid>
            <category><![CDATA[search-engines]]></category>
            <category><![CDATA[google]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Sat, 17 Feb 2024 20:30:15 GMT</pubDate>
            <atom:updated>2024-02-17T20:30:15.069Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*5WRtVeDgxhtF7FtoIfqpkQ.png" /><figcaption>Google nos anos 2000</figcaption></figure><p>Você já parou para refletir sobre como a internet mudou drasticamente nossas vidas? Quando o Google surgiu e revolucionou completamente a maneira como buscamos informações online. Era como se um mundo de conhecimento estivesse literalmente ao alcance dos nossos dedos, e tudo isso graças aos complexos algoritmos por trás daquela simples caixa de pesquisa.</p><p><strong>IA? AI?</strong></p><p>Mas e agora, com o avanço da Inteligência Artificial (IA)? Assim como o Google abalou os primórdios da internet com sua ferramenta de busca, o mesmo fez a OpenAI quando lançou o ChatGPT, uma ferramenta que transforma a forma como interagimos com a internet, com isso, ao invés de passar horas navegando por páginas e páginas em busca de respostas, agora podemos simplesmente fazer uma pergunta e receber uma resposta direta. É como ter um assistente pessoal sempre disponível, embora claro, ainda há limitações.</p><p>E não é apenas o ChatGPT que está mudando a forma como interagimos com a internet, o TikTok, por exemplo, criou nova maneira para as gerações mais jovens acessarem informações, substituindo a tradicional busca na web por uma experiência mais imersiva e visual com seus vídeos, misturando a primordial busca na internet com vídeos curtos cheios de informações.</p><p>Mas e o futuro? Bem, parece que a Microsoft está pronta para dar mais um passo à frente. Ao reconhecer o potencial dos modelos de IA desenvolvidos pela OpenAI, a empresa apresentou no Bing, seu motor de busca rival ao Google, um novo conceito de busca online. Com essa experiencia já disponível, e utilizando um conceito chamado de RAG (i.e. Retrieval Augmented Generation), o mecanismo de busca que não apenas fornece links e textos com base em palavras chave, mas uma experiência inovadora gerando um prompt otimizado para o modelo de IA com base os principais resultados da busca tradicional. Como disse uma vez Jeff Bezos, os clientes/usuários nunca estarão satisfeitos, eles estarão sempre buscando por algo melhor, uma experiencia melhor, mesmo que nem eles saibam exatamente o que é. Essa busca incessante por melhorias, junto com claro, a concorrência de empresas, que impulsiona a inovação da tecnologia no mundo.</p><p>E falando de Jeff Bezos, o fundador da Amazon fez um investimento em uma empresa que está sendo conhecida por ser rival a busca tradicional oferecida pela Google, e a busca baseada por IA pela Microsoft, a empresa <a href="https://www.perplexity.ai">Perplexity AI</a>. Trazendo uma experiencia similar ao que a Microsoft tem feito com o Bing AI, utilizando o conceito de RAG para realizar o prompt no modelo, para oferecer uma experiencia de respostas geradas por IA Generativa com base os principais e mais relevantes resultados da busca.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cGM2arHwfgIBP-MPDna2YQ.gif" /><figcaption>Um exemplo da experiência de busca generativa com a Perplexity.AI</figcaption></figure><p><strong>Isso nos leva a pensar…</strong></p><p>À medida que nos aproximamos desse novo capítulo da busca na internet, com a IA moldando o caminho, assim como muitas outras ferramentas que estão aparecendo pelo boom de IA, só podemos esperar que essa revolução nos traga uma experiência ainda mais intuitiva, personalizada e eficiente. Assim como antes do Google poderíamos gastar centenas de horas para encontrar uma informação, e com a ferramenta ver isso ser reduzido para minutos, o futuro com ferramentas como a Perplexity AI nos promete talvez segundos, com uma experiencia cada vez mais inovadora. E claro, o Google com certeza não deixará a concorrência na frente, com isso o futuro parece estar chegando, e nós seremos os mais beneficiados com a concorrência das big techs. E ai, você está preparado?</p><p>Se chegou até aqui, deixe sua curtida :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66fa710900d8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Importando recursos existentes no Terraform e gerando sua declaração automaticamente]]></title>
            <link>https://medium.com/@davidalecrim1/importando-recursos-existentes-no-terraform-e-gerando-sua-declara%C3%A7%C3%A3o-automaticamente-330ec66d4efe?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/330ec66d4efe</guid>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[iac]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cloud]]></category>
            <category><![CDATA[nuvem]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Sat, 03 Feb 2024 21:47:32 GMT</pubDate>
            <atom:updated>2024-02-03T22:03:31.532Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tuip2licWhNdIFk_4ZrNXA.png" /><figcaption>Logo do Terraform pela Hashicorp</figcaption></figure><p>Um dos desafios que as equipes de tecnologia que possuem recursos na nuvem enfrentam, é que se caso os recursos não foram criados inicialmente se baseando em infraestrutura como código (ou seja, IaC, utilizando ferramentas como o Terraform), além de perder os diversos benefícios, como por exemplo, o reuso em outros projetos, podem enfrentar dores de cabeça ao introduzir essa nova forma de metodologia nos seus ciclos de desenvolvimento, onde é aproximado a infraestrutura como código junto aos repositório das aplicações e seus pipelines.</p><p>O problema comum é a equipe já ter infraestrutura rodando em produção, como agora trazer esses recursos para o ciclo de vida de um pipeline de infraestrutura como código, controlado pelo arquivo .tfstate, e como evitar possíveis impactos que isso pode trazer. Por exemplo, supondo que se tem um bucket S3 em ambiente produtivo na nuvem da AWS, uma das alternativas durante a migração para infraestrutura como código é criar um novo bucket e depois transitar na aplicação, mas essa estratégia pode trazer algumas dores de cabeça, além de ser uma estratégia custosa olhando as tarefas a serem realizas, então como podemos avisar o Terraform que os recursos já existem pensando em evitar downtime e retrabalho?</p><p><strong>Exemplificando o Problema</strong></p><p>Vamos considerar a criação do bucket utilizando Terraform:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b2ef60bb867c1a237234c75d67237e1b/href">https://medium.com/media/b2ef60bb867c1a237234c75d67237e1b/href</a></iframe><p>Porém lembre-se do problema, é se a aplicação utilizando a infraestrutura já é produtiva, o que podemos fazer? Como podemos ver abaixo, o bucket já existe na conta AWS em uso.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QzIfDm_LYtRdlKjdufxvPQ.png" /><figcaption>Imagem do console AWS com bucket chamado &quot;meu-bucket-aws-demo-01&quot;.</figcaption></figure><p>Agora, vamos executar `terraform plan`, e ver o que acontece.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b82676c728d469f1a7700fc52a32c6f1/href">https://medium.com/media/b82676c728d469f1a7700fc52a32c6f1/href</a></iframe><p>Veja que mesmo o nome estando exatamente igual ao criado na conta AWS configurada no terminal, ele ainda considera no ciclo de vida do Terraform que o recurso não existe. Caso rodarmos um terraform apply será apontado erro na API da AWS.</p><p><strong>Mas há uma alternativa?</strong></p><p>Pensando nesse cenário que na versão 1.5 do Terraform que foi introduzido o recurso de <strong>import</strong>, em um bloco simples, é possível dizer ao ciclo de vida do Terraform que aquele recurso já existe, e por isso só deve ser alterado se tiver modificações declaras em seu código. Vamos ver como fica no código abaixo.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c2f422940b61ebdb8410b4ab8e265efe/href">https://medium.com/media/c2f422940b61ebdb8410b4ab8e265efe/href</a></iframe><p>O id é o identificador único de um recurso na AWS, nesse caso, é o nome do bucket. O to é para qual recurso declarado no terraform realizar um link.</p><p>Agora, tão simples quanto, podemos rodar novamente o terraform plan e validar se o ciclo de vida do Terraform irá reconhecer nosso bucket.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/30b903de55b979828cc8bea8ca382449/href">https://medium.com/media/30b903de55b979828cc8bea8ca382449/href</a></iframe><p>Agora sim, o ciclo de vida do Terraform entendeu que nosso bucket está criado na AWS, e irá importá-lo dentro do bloco meu_bucket , permitindo que possamos realizar alterações (como por exemplo adicionar tags do projeto) já utilizando o pipeline de infraestrutura com Terraform.</p><p><strong>Mas será que tem como ficar melhor?</strong></p><p>Além dessa facilidade, será que é possível &quot;importar&quot; a declaração completa do recurso? Pois como fizemos acima, por mais que o ciclo de vida do Terraform entenda a existência do recurso, não o cria automaticamente na nossa declaração dentro do main.tf.</p><p>Pensando nisso que foi também introduzido o recurso de gerar as configurações durante o estágio de terraform plan, onde é possível gerar a declaração do seu recurso de infraestrutura, e combinar (ou não) com o import de um recurso existente como fizemos acima. Para isso, basta ter um import como a seguir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/93f5d9dde9391fdab81657a6091d1fa9/href">https://medium.com/media/93f5d9dde9391fdab81657a6091d1fa9/href</a></iframe><p>Após isso, no exemplo acima vamor importar uma IAM Role que foi criada via console, e agora conseguimos gerar a declaração com o comando a seguir.</p><pre>terraform plan -generate-config-out=&#39;generated.tf&#39;</pre><p>Como mencionado no comando, será criado um arquivo chamado generated.tf , vamos vê-lo a seguir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d752869432fcd7caf4ba3b91956cf2f/href">https://medium.com/media/0d752869432fcd7caf4ba3b91956cf2f/href</a></iframe><p><strong>Conclusão</strong></p><p>Com ambos recursos de import e generate-config-out, conseguimos avisar o ciclo de vida do Terraform que o recurso existe, seja por qual motivo for, e além disso também gerar a declaração do recurso, tornando tudo bem mais fácil na migração da infraestrutura criada via console para infraestrutura como código.</p><p>Caso tenha lido até aqui, deixe sua curtida se gostou do conteúdo! Até a próxima!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=330ec66d4efe" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Como fazer Web Crawling e Web Scraping com Node.js e o Puppeteer]]></title>
            <link>https://medium.com/@davidalecrim1/como-fazer-web-crawling-e-web-scraping-com-node-js-e-o-puppeteer-b914ab2fe481?source=rss-bc899f49119b------2</link>
            <guid isPermaLink="false">https://medium.com/p/b914ab2fe481</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[David Alecrim]]></dc:creator>
            <pubDate>Fri, 24 Feb 2023 22:25:44 GMT</pubDate>
            <atom:updated>2023-02-25T16:11:59.151Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iA5KaVF_VnjXMuJrDtIBVw.png" /><figcaption>Figura 1 — Robô em um browser — Imagem gerada no DALL-E pelo autor</figcaption></figure><p>Todo mundo em algum momento já precisou “dar um Google”, buscar alguma informação que precisava, e achar o site onde pudesse se aprofundar no conteúdo desejado. Porém algo que não paramos para pensar durante essas consultas na internet é como essa indexação de conteúdos de bilhões de sites da internet é feita, e como os sites de mecanismos de busca, ou seja Bing, Google, Yandex e entre outros fazem essa apresentação de diversos resultados para nós, e ainda mais, como ela funciona?</p><p>Neste artigo, vamos entender mais a fundo o que está “em baixo do capô” dos robôs que indexam conteúdos para os mecanismos de busca, e entender como o web crawling e web scraping são utilizados para as finalidades de indexação de conteúdos.</p><h3><strong>O Puppeteer</strong></h3><p>O Puppeteer é uma produto para automação de browser, disponível para o Node.js e outras linguagens, ele fornece uma maneira fácil de controlar um navegador de forma automatizado. Com o Puppeteer, você pode criar scripts que navegam os sites da internet, e interagir com elementos da página, coletar informações e muito mais. Além disso, o Puppeteer é compatível com a maioria dos navegadores populares, como o Chrome, o Firefox e o Safari.</p><p>Com essas propriedades, o Puppeteer permite que possamos realizar web crawling e web scraping em sites da internet.</p><h3><strong>Web Crawling vs. Web Scraping</strong></h3><p>Antes de começarmos, vamos esclarecer a diferença entre web crawling e web scraping.</p><p>O web crawling é o processo de coletar dados de vários sites da internet de maneira sistemática. É como se você estivesse rastreando a internet em busca de informações. O objetivo do web crawling é coletar o máximo de dados possível em um determinado assunto, seja para fins de pesquisa, análise de mercado ou outra finalidade.</p><p>Já o web scraping é o processo de extrair informações específicas de uma site da internet. O objetivo do web scraping é coletar dados relevantes de uma página da web para uma finalidade específica, como monitorar preços de produtos, coletar informações de contato, coletar dados de uma tabela, ou automatizar a tarefas que seriam realizadas de forma manual por um usuário.</p><p>Agora que sabemos a diferença entre web crawling e web scraping, vamos ver como usar o Puppeteer para realizar essas tarefas.</p><h3><strong>Instalação do Puppeteer</strong></h3><p>Para começar, precisamos instalar o Puppeteer. Você pode instalar o Puppeteer usando o <em>npm</em>, o gerenciador de pacotes do Node.js.</p><pre>npm install puppeteer</pre><p>Depois de instalado, podemos começar a usar o Puppeteer em nossos scripts.</p><h3><strong>Exemplo de Web Crawling</strong></h3><p>Vamos começar com um exemplo de web crawling. Neste exemplo, vamos usar o Puppeteer para coletar todas as URLs em uma página da web e, em seguida, seguir cada URL e coletar todas as URLs em cada página vinculada.</p><p>Para isso, vamos criar um arquivo index.js conforme a seguir.</p><pre>const puppeteer = require(&#39;puppeteer&#39;);<br><br>(async () =&gt; {<br><br>  //Abre uma sessão no browser e uma aba<br>  const browser = await puppeteer.launch();<br>  const page = await browser.newPage();<br><br>  const urls = new Set();<br>  const queue = new Set();<br><br>  const crawlingDepthLimit = 5;<br><br>  queue.add(&#39;https://www.medium.com/&#39;);<br><br>  while (queue.size &gt; 0) {<br><br>    const url = queue.values().next().value;<br><br>    //Remove a próxima URL da fila e adiciona nos resultados<br>    queue.delete(url);<br>    urls.add(url);<br><br>    //Para de adicionar novas URLs na fila caso tenha chegado no limite configurado<br>    if (urls.size &lt; crawlingDepthLimit){<br><br>      //Acessa a página informada da fila atual<br>      await page.goto(url);<br><br>      //Mapeia elementos HTML de links para adicionar a fila<br>      const newUrls = await page.$$eval(&#39;a&#39;, links =&gt;<br>      links.map(link =&gt; link.href)<br>      );<br>  <br>      //Verificar duplicidades de URLs antes de adicionar na fila<br>      newUrls.forEach(newUrl =&gt; {<br>        if (!urls.has(newUrl) &amp;&amp; !queue.has(newUrl)) {<br>          queue.add(newUrl);<br>        }<br><br>      });<br>    }<br>  }<br><br>  //Fecha o browser<br>  await browser.close();<br><br>  console.log([...urls]);<br><br>})();</pre><p>Este script abre o site do <a href="https://www.medium.com">https://www.medium.com</a> em uma instância do navegador controlado pelo Puppeteer e coleta todas as URLs da página. Em seguida, adiciona cada URL à lista de URLs coletadas e adiciona todas as URLs recém-descobertas a uma fila para processamento posterior.</p><p>Em seguida, o script segue cada URL na fila e repete o processo, coletando todas as URLs em cada página vinculada e adicionando-as à fila. Como boa prática, foi adicionado um limite de crawling para evitar tempo de execução extensivo.</p><p>Executando esse script com Node.js, executando o comando node index.js temos o seguinte resultado:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qsg4s6o6SnybAYjq4KlFIA.png" /><figcaption>Figura 2 — Resultado do script — Imagem do autor</figcaption></figure><h3><strong>Exemplo de Web Scraping</strong></h3><p>Já para termos um exemplo de script para web scraping, o script a seguir abre a página inicial do site <a href="https://www.example.com/">https://www.example.com/</a> e extrai o texto do título da página (elemento &lt;h1&gt;) e o texto do primeiro parágrafo da página (elemento &lt;p&gt;). Em seguida, ele exibe essas informações no terminal.</p><pre>const puppeteer = require(&#39;puppeteer&#39;);<br><br>(async () =&gt; {<br><br>  //Abre uma sessão no browser e uma aba<br>  const browser = await puppeteer.launch();<br>  const page = await browser.newPage();<br><br>  //Acessa a página informada<br>  await page.goto(&#39;https://www.example.com/&#39;);<br><br>  //Coleta elementos HTML da página<br>  const title = await page.$eval(&#39;h1&#39;, el =&gt; el.innerText);<br>  const paragraph = await page.$eval(&#39;p&#39;, el =&gt; el.innerText);<br><br>  //Tira um print de tela do site no browser<br>  page.screenshot({path: `screenshot.png`})<br><br>  console.log(title);<br>  console.log(paragraph);<br><br>  //Fecha o browser<br>  await browser.close();<br>})();</pre><p>Executando esse script com Node.js, executando o comando node index.js temos o seguinte resultado:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*55vfStSsoU6nrcFruy0qHQ.png" /><figcaption>Figura 3— Resultado do Script — Imagem do autor</figcaption></figure><p>Uma das funcionalidades é tirar capturas de tela da sessão do browser controlada pelo Puppeteer, como no exemplo a seguir com a página do <a href="https://www.example.com">https://www.example.com</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*Iapf2ycVUhLlo_-hx3WVYA.png" /><figcaption>Figura 4 — Captura de Tela com Puppeteer — Imagem do Autor</figcaption></figure><p>Estes são apenas exemplos simples, mas com possibilidades ilimitadas, pois poderíamos extrair uma grande quantidade de informações úteis a partir de sites, tais como: preços de produtos, informações de contato, dados de tabelas, dentre outros. Assim como, realizar automações para tarefas repetitivas, ou testes de interface de usuário simulando um cliente final, onde poderíamos executar cliques em botões, e fazer uma experiência completa.</p><h3><strong>Conclusão</strong></h3><p>O Puppeteer é uma ótima ferramenta para web crawling e web scraping em sites da internet. Ele nos permite automatizar ações no navegador, interagir com elementos da página e extrair informações específicas. Com o Puppeteer, podemos facilmente coletar dados de uma variedade de fontes, como sites de notícias, plataformas de mídia social, sites de comércio eletrônico e muito mais.</p><p>No entanto, é importante lembrar que o web scraping deve ser realizado de forma ética e legal. Alguns sites podem ter termos de serviço que proíbem o scraping de seus dados ou podem considerar o scraping de seus dados como uma violação de seus direitos autorais. Vale ressaltar também que muitos sites hoje em dia contém com mecanismos anti robôs, como o reCAPTCHA da Google por exemplo, que identifica comportamento de robôs como os que criamos acima, e realiza o bloqueio destes scripts.</p><p>Após a compreensão dos conceitos de web crawling e web scraping e seu uso, conseguimos entender como as informações dos sites da internet são extraídas e indexadas por mecanismos de busca, e entendendo que quando um determinado <strong>termo, texto ou frase é buscado</strong> (por exemplo, “O que é Node.js?”), o mecanismo busca na “última versão” indexada dos sites públicos da internet, que foram indexadas por robôs como os que criamos anteriormente.</p><p>Com a imagem a seguir, conseguimos visualizar diversas informações de vários sites da internet, e como os mecanismo de buscam usam o que foi indexado (ou seja, salva e manipula o HTML puro como fizemos acima) para trazer uma experiência customizada para o usuário.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c0kzthbzKwHH9ztBVVopxw.png" /><figcaption>Figura 5 — Exemplo de busca no Google.com — Imagem do autor</figcaption></figure><p>Se chegou até aqui, deixe seu 👍 para saber que curtiu o artigo!</p><p>Me siga para mais postagens! 😃</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b914ab2fe481" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>