Create a serverless scraping architecture, with Scaleway Queues, Serverless Functions, and Managed Database.
Reviewed on 21 October 2024 • Published on 08 December 2023
terraform
scraping-architecture
In this tutorial, we show how to set up a simple application that reads Hacker News and processes the articles it finds there asynchronously. To do so, we use Scaleway serverless products and deploy two functions:
A producer function, activated by a recurrent cron trigger, that scrapes HackerNews for articles published in the last 15 minutes and pushes the title and URL of the articles to a queue created with Scaleway Queues.
A consumer function, triggered by each new message on the queue, that consumes messages published to the queue, scrapes some data from the linked article, and then writes the data into a Scaleway Managed Database.
We show how to provision all the required Scaleway resources via Terraform, but you can also use the console, the API or one of our other supported developer tools. The code for the functions is written in Python.
This project exemplifies a decoupled architecture, where producer and consumer perform specific tasks independently. This kind of design is modular and allows for flexibility and scalability. It also adheres to the principles of microservices and serverless architectures, where individual functions or scripts focus on specific tasks.
You can find all of the code on GitHub, so feel free to dive right in, or else follow along with the step-by-step explanation below to create the architecture yourself.
We start by creating the scraper program, or the “data producer”.
Create a directory called scraper.
Inside it, create a file requirements.txt. We need the packages requests, bs4, and boto3, as follows:
boto3
bs4
requests
Create a subdirectory handlers within the scraper directory, and add a scrape_hn.py file inside it.
Complete the scrape_hn.py file by adding the function code as shown in the repository. The main code blocks are briefly explained as follows:
Queue credentials and URL are read by the function from environment variables. Those variables are set by Terraform as explained in one of the next sections. If you choose another deployment method, such as the console, do not forget to set them.
Use the requests package to retrieve the HTML, and BeautifulSoup to parse it and extract the span we are interested in: titleline containing title and url, and age holding the publication time.
defscrape_and_push():
page = requests.get(HN_URL)
html_doc = page.content
soup = BeautifulSoup(html_doc,'html.parser')
titlelines = soup.find_all(class_="titleline")
ages = soup.find_all(class_="age")
Using the AWS python sdk boto3, connect to the queue and push the title and url of articles published less than 15 minutes ago.
sqs = boto3.client(
'sqs',
endpoint_url=SCW_SQS_URL,
aws_access_key_id=sqs_access_key,
aws_secret_access_key=sqs_secret_access_key,
region_name='fr-par')
for age, titleline inzip(ages, titlelines):
time_str = age["title"]
time = datetime.strptime(time_str,"%Y-%m-%dT%H:%M:%S")
if datetime.utcnow()- time > timedelta(minutes=15):
continue
body = json.dumps({'url': titleline.a["href"],'title': titleline.a.get_text()})
As explained in the Scaleway Functions documentation, dependencies need to be installed in a package folder, and uploaded as an archive. This is done via Terraform, but if you decide to deploy using another system, you can create the archive as follows:
Next, let’s create our consumer function. When receiving a message containing the article’s title and URL from the queue, it will scrape the page for some stats (number of a, h1, and p tags) and save these values in a Scaleway Managed PostgreSQL Database instance. We show how to create the database instance (and other underlying resources) via Terraform in the following steps. If you opt for another method to create the underlying resources, e.g. the console or API, do not forget to set the relevant function environment variables.
Create a directory called consumer (at the same level as the scraper directory previously created).
Inside it, create a file requirements.txt. We need the packages requests, bs4, and pg8000, as shown. We use pg8000 as it does not depend on system packages, thus making it a good fit for a serverless function:
pg8000
requests
bs4
Create a subdirectory handlers within the consumer directory, and add a consumer.py file inside it.
Complete the consumer.py file by adding the function code as shown in the repository. The main code blocks are briefly explained as follows:
Queue credentials and URLs are accessed by the function as environment variables. These variables are set by Terraform as explained in one of the next sections. If you choose another deployment method, such as the console, do not forget to set them.
db_host = os.getenv('DB_HOST')
db_port = os.getenv('DB_PORT')
db_name = os.getenv('DB_NAME')
db_user = os.getenv('DB_USER')
db_password = os.getenv('DB_PASSWORD')
From the trigger event we can read the body of the message, and use the passed URL to scrape the page for stats.
defscrape_and_save_to_db(event):
body = json.loads(event["body"])
tags_count = scrape_page_for_stats(body['url'])
Lastly, we write the information into the database. To keep the whole process completely automatic theCREATE_TABLE_IF_NOT_EXISTSquery is run each time. If you integrate the functions into an existing database, there is no need for it.
As explained in the Scaleway Functions documentation, dependencies need to be installed in a package folder, and uploaded as an archive. This is done via Terraform, but if you decide to deploy using another system, you can create the archive as follows:
For the purposes of this tutorial, we show how to provision all resources via Terraform.
Tip
If you do not want to use Terraform, you can also create the required resources via the console, the Scaleway API, or any other developer tool. Remember that if you do so, you will need to set up environment variables for functions as previously specified. The following documentation may help create the required resources:
Still in the same file, add the code below to provision the queue resources: separate credentials with appropriate permissions for producer and consumer, and a Scaleway queue:
Add the code below to provision the Managed Database for PostgreSQL resources. Note that here we are creating a random password and using it for the default and worker user:
Add the code below to provision the functions resources. First, activate the namespace, then locally zip the code and create the functions in the cloud. Note that we are referencing variables from other resources, to completely automate the deployment process:
Note that a folder archives needs to be created manually if you started from scratch. It is included in the git repository.
Add the code below to provision the triggers resources. The cron trigger activates at the minutes [0, 15, 30, 45] of every hour. No arguments are passed, but we could do so by specifying them in JSON format in the args parameter.
Go to the Scaleway console, and check the logs and metrics for Serverless Functions’ execution and the queue’s statistics.
To make sure the data is correctly stored in the database, you can connect to it directly via a CLI tool such as psql.
Retrieve the instance IP and port of your Managed Database from the console, under the Managed Database section.
Use the following command to connect to your database. When prompted for a password, you can find it by running terraform output -json.
We have shown how to asynchronously decouple the producer and the consumer using Scaleway Queues, adhering to serverless patterns.
While the volume of data processed in this example is quite small, thanks to robustness of Scaleway Queues and the auto-scaling capabilities of the Serverless Functions, you can adapt this example to manage larger workloads.
Here are some possible extensions to this basic example:
Replace the simple proposed logic with your own. What about counting how many times some keywords (e.g: copilot, serverless, microservice) appear in Hacker News articles?
Define multiple cron triggers for different websites and pass the website as an argument to the function. Or, create multiple functions that feed the same queue.
Use a Serverless Container instead of the consumer function, and use a command line tool such as htmldoc or pandoc to convert the scraped articles to PDF and upload the result to a Scaleway Object Storage bucket.
Replace the Managed Database for PostgreSQL with a Scaleway Serverless Database, so that all the infrastructure lives in the serverless ecosystem! Note that at the moment there is no Terraform support for Serverless Database, hence the choice here to use Managed Database for PostgreSQL.
Thank you for the feedback!
Your opinion helps us make a better documentation.