From fe5ad26655fc92d367248ad624c0db696540ffb7 Mon Sep 17 00:00:00 2001 From: PreethamShastry Date: Tue, 9 Jul 2024 15:09:44 +0530 Subject: [PATCH 1/2] first session commit --- Dockerfile | 17 - docker-compose.yaml | 26 -- docker-compose.yml | 31 ++ dockerfile | 14 + docs/gitflow.png | Bin 32865 -> 0 bytes docs/introduction_to_docker.md | 131 ------ docs/introduction_to_git_commands.md | 58 --- docs/introduction_to_postgresql.md | 175 ------- docs/introduction_to_webscraping.md | 194 -------- docs/working_with_docker_container.md | 70 --- docs/workshop1_home_work.md | 7 - prerequisites/install_docker.sh | 630 -------------------------- requirements.txt | 6 +- web_scraping.py | 99 ++++ web_scraping_sample.py | 31 -- 15 files changed, 146 insertions(+), 1343 deletions(-) delete mode 100644 Dockerfile delete mode 100644 docker-compose.yaml create mode 100644 docker-compose.yml create mode 100644 dockerfile delete mode 100644 docs/gitflow.png delete mode 100644 docs/introduction_to_docker.md delete mode 100644 docs/introduction_to_git_commands.md delete mode 100644 docs/introduction_to_postgresql.md delete mode 100644 docs/introduction_to_webscraping.md delete mode 100644 docs/working_with_docker_container.md delete mode 100644 docs/workshop1_home_work.md delete mode 100755 prerequisites/install_docker.sh create mode 100644 web_scraping.py delete mode 100644 web_scraping_sample.py diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f07ae2b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM python:3.10.2-alpine3.15 -COPY . . -# Install Postgres -RUN apk update -RUN apk add postgresql -RUN chown postgres:postgres /run/postgresql/ -# Install requirements -COPY ./requirements.txt /tmp -RUN pip install -r /tmp/requirements.txt -# For psycopg2 -RUN apk add --virtual postgresql-deps libpq-dev -# Create directories -RUN mkdir -p /root/workspace/src -# Mount your local file -COPY ./web_scraping_sample.py /root/workspace/src -# Switch to project directory -WORKDIR /root/workspace/src \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index cad1491..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,26 +0,0 @@ -version: "3" -services: - pyhton_service: - build: - context: ./ - dockerfile: Dockerfile - image: workshop1 - container_name: workshop_python_container - stdin_open: true # docker attach container_id - tty: true - ports: - - "8000:8000" - volumes: - - .:/app - depends_on: - - postgres_service - - postgres_service: - image: postgres - container_name: workshop_postgres_container - ports: - - "5432:5432" - environment: - POSTGRES_PASSWORD: admin - volumes: - - .:/var/lib/postgres diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..70b3139 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3' + +services: + db: + image: postgres:latest + container_name: python-blog-db + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5434:5432" + volumes: + - db-data:/var/lib/postgresql/data + + web: + build: + context: . + container_name: python-blog-scraper + depends_on: + - db + environment: + DB_NAME: postgres + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: db + DB_PORT: "5432" + +volumes: + db-data: + driver: local diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..665f902 --- /dev/null +++ b/dockerfile @@ -0,0 +1,14 @@ +# Use an official Python runtime as a parent image +FROM python:3.9-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy only the necessary files into the container +COPY web_scraping.py /app/ + +# Install any needed packages specified in requirements.txt +RUN pip install --no-cache-dir requests bs4 psycopg2-binary + +# Run the Python script when the container launches +CMD ["python", "./web_scraping.py"] diff --git a/docs/gitflow.png b/docs/gitflow.png deleted file mode 100644 index b416d14b26ba4b61a0dc57e25e357d74b699913c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32865 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 diff --git a/docs/introduction_to_docker.md b/docs/introduction_to_docker.md deleted file mode 100644 index cd43f48..0000000 --- a/docs/introduction_to_docker.md +++ /dev/null @@ -1,131 +0,0 @@ -# Introduction to Docker - -## Video : Introduction to Docker and Containers - - -Docker is a container management service. The keywords of Docker are develop, ship and run anywhere. The whole idea of Docker is for developers to easily develop applications, ship them into containers which can then be deployed anywhere. - -[![Video : Introduction to Docker and Containers](https://i3.ytimg.com/vi/JSLpG_spOBM/hqdefault.jpg)](https://www.youtube.com/watch?v=JSLpG_spOBM&ab_channel=RyanSchachte) - -
- -## Features of Docker - -- Docker has the ability to reduce the size of development by providing a smaller footprint of the operating system via containers. - -- With containers, it becomes easier for teams across different units, such as development, QA and Operations to work seamlessly across applications. - -- You can deploy Docker containers anywhere, on any physical and virtual machines and even on the cloud. - -- Since Docker containers are pretty lightweight, they are very easily scalable. - -
- -## Docker post-installation setup -Do the optional procedure configuration to work better with Docker. - -### Run Docker as non-root user -To create the docker group and add your user: -1. Create the docker group. -``` -sudo groupadd docker -``` -2. Add your user to the docker group. -``` -sudo usermod -aG docker $USER -``` - -3. Activate the changes to groups: -``` -newgrp docker -``` -4. Verify that you can run docker commands without sudo. -``` -docker images -``` - -
- -## Docker Commands -Docker is a containerization system which packages and runs the application with its dependencies inside a container. There are several docker commands you must know when working with Docker. -### 1. Docker version -To find the installed docker version -Command: -``` -docker --version -``` -##### **_Docker version 20.10.12, build e91ed57_** - - -
- -### 2. Downloading image -To work with any ocker image we need to download the docker image first.
-Command: -``` -docker pull -``` -Example of pulling alpine:latest image -``` -docker pull alpine:latest -``` - _Note: You may find 1000s of docker images in [Docker Hub](https://hub.docker.com/)_ - -
- -### 3. List all the docker images -To list all the images that is locallt available in the host machine, simply run the below command. This will list all the docker images in the local system. -
-Command: -``` -docker images -``` -``` -Example: -REPOSITORY TAG IMAGE ID CREATED SIZE -alpine latest c059bfaa849c 6 weeks ago 5.59MB -``` -
- -### 4. Run docker image -The docker run command first creates a writeable container layer over the specified image, and then starts it using the specified command. -
-Command: -``` -docker run [options] -``` -> Explore here: [https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04) - - -Example of running alpine:latest image, the options -t allows us to access the terminal and -i gets stdin stream added. Basicaly using -ti adds the terminal driver. -``` -docker run -t -i alpine:latest -``` -OR -``` -docker run -ti alpine:latest -``` -_Note: You can use Ctrl+D to come out from the docker image._ - -
- -## Create docker image for python:3.10.2-alpine3.15 - -Create a new file called _**Dockerfile**_ and then paste the below content -``` -FROM python:3.10.2-alpine3.15 -# Create directories -RUN mkdir -p /root/workspace/src -# Switch to project directory -WORKDIR /root/workspace/src -``` -Goto the directory where you created **Dockerfile** -``` -docker build ./ -t simple_python -``` -You may check the image you created using `docker images` command - -Run the _**simple_python**_ image you created -``` -docket run -ti simple_python -``` \ No newline at end of file diff --git a/docs/introduction_to_git_commands.md b/docs/introduction_to_git_commands.md deleted file mode 100644 index 512cef0..0000000 --- a/docs/introduction_to_git_commands.md +++ /dev/null @@ -1,58 +0,0 @@ -# Introduction to Git -
- -### Setting up github: - -``` -Make a repository in GitHub - - Go to GitHub.com and login. - Click the green “New Repository” button - Repository name: myrepo - Public - Check Initialize this repository with a README - Click the green “Create repository” button - Copy the HTTPS clone URL to your clipboard via the green “Clone or Download” button. -Clone the repository to your computer - - git clone git@github.com:[username]/[repository_name].git - -Make a local change, commit, and push - - git add //here the file path is which file you modified ready the file for commit - git commit -m "A commit from my local computer" //here you commit the changes - git push origin //here you push the changes to your remote repository and brach name is in which brach you pushing this -``` -
- -### General git flow: -![git flow](gitflow.png) - -``` -Basic commands of git: - -git init -the command git init is used to create an empty Git repository. - -git add - Add command is used after checking the status of the files, to add t hose files to the staging area. - Before running the commit command, "git add" is used to add any new or modified files. - -git commit - The commit command makes sure that the changes are saved to the local repository. - The command "git commit –m " allows you to describe everyone and help them understand what has happened. - -git status - The git status command tells the current state of the repository. - The command provides the current working branch. If the files are in the staging area, but not committed, it will be shown by the git status. - Also, if there are no changes, it will show the message no changes to commit, working directory clean. - -git config -The git config command is used initially to configure the user.name and user.email. This specifies what email id and username will be used from a local repository. -``` - -### Creating a pull request -A pull request – also referred to as a merge request – is an event that takes place in software development when a contributor/developer is ready to begin the process of merging new code changes with the main project repository. - ->Refer this link (from "Create a New Branch") : https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github - diff --git a/docs/introduction_to_postgresql.md b/docs/introduction_to_postgresql.md deleted file mode 100644 index d7dcfd0..0000000 --- a/docs/introduction_to_postgresql.md +++ /dev/null @@ -1,175 +0,0 @@ -# Introduction to PostgreSQL -``` -Key Features of PostgreSQL: -- Free to download -- Compatible with Data Integrity -- Compatible with multiple data types -- Highly extensible -- Secure -- Highly Reliable -``` -
- - -### JOINS -``` -- The CROSS JOIN -- The INNER JOIN -- The LEFT OUTER JOIN -- The RIGHT OUTER JOIN -- The FULL OUTER JOIN -``` - - ![Pictorial Representation of JOINS](https://i.stack.imgur.com/4zjxm.png) - -
- -``` -The CROSS JOIN - A CROSS JOIN matches every row of the first table with every row of the second table - SELECT ... FROM table1 CROSS JOIN table2 … - SELECT EMP_ID, NAME, DEPT FROM COMPANY CROSS JOIN DEPARTMENT; - -The INNER JOIN - A INNER JOIN creates a new result table by combining column values of two tables (table1 and table2) based upon the join-predicate. The query compares each row of table1 with each row of table2 to find all pairs of rows, which satisfy the join-predicate. When the join-predicate is satisfied, column values for each matched pair of rows of table1 and table2 are combined into a result row. - SELECT table1.column1, table2.column2... - FROM table1 - INNER JOIN table2 - ON table1.common_filed = table2.common_field; - - SELECT EMP_ID, NAME, DEPT FROM COMPANY INNER JOIN DEPARTMENT ON COMPANY.ID = DEPARTMENT.EMP_ID; - -The LEFT OUTER JOIN - The OUTER JOIN is an extension of the INNER JOIN. SQL standard defines three types of OUTER JOINs: LEFT, RIGHT, and FULL and PostgreSQL supports all of these. - In case of LEFT OUTER JOIN, an inner join is performed first. Then, for each row in table T1 that does not satisfy the join condition with any row in table T2, a joined row is added with null values in columns of T2. Thus, the joined table always has at least one row for each row in T1. - - SELECT ... FROM table1 LEFT OUTER JOIN table2 ON conditional_expression ... - - SELECT EMP_ID, NAME, DEPT FROM COMPANY LEFT OUTER JOIN DEPARTMENT - ON COMPANY.ID = DEPARTMENT.EMP_ID; - -The RIGHT OUTER JOIN - First, an inner join is performed. Then, for each row in table T2 that does not satisfy the join condition with any row in table T1, a joined row is added with null values in columns of T1. This is the converse of a left join; the result table will always have a row for each row in T2. - SELECT ... FROM table1 RIGHT OUTER JOIN table2 ON conditional_expression ... - - SELECT EMP_ID, NAME, DEPT FROM COMPANY RIGHT OUTER JOIN DEPARTMENT ON COMPANY.ID = DEPARTMENT.EMP_ID; - -The FULL OUTER JOIN - First, an inner join is performed. Then, for each row in table T1 that does not satisfy the join condition with any row in table T2, a joined row is added with null values in columns of T2. In addition, for each row of T2 that does not satisfy the join condition with any row in T1, a joined row with null values in the columns of T1 is added. - The following is the syntax of FULL OUTER JOIN − - SELECT ... FROM table1 FULL OUTER JOIN table2 ON conditional_expression ... - Based on the above tables, we can write an inner join as follows − - SELECT EMP_ID, NAME, DEPT FROM COMPANY FULL OUTER JOIN DEPARTMENT ON COMPANY.ID = DEPARTMENT.EMP_ID; - -``` -
- -``` -Things to Note -- You can do select with limit -- You are able to do group by, order by ,having clauses, etc. -- Your not able to limit delete and update directly. You need to use inner query - > delete from student where sid in (select id from table limit 10) - > update from student set city=”mangalore”where sid in (select id from table limit 10) -``` -
- -## Create PostgresSql docker container -- Add below container in existing **_docker-compose.yml_** file - ``` - psql-db: - image: 'postgres:14' - container_name: psql-db - environment: - - PGPASSWORD=123456 - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=123456 - ports: - - '5434:5432' - ``` - - -- Get the containers up. - ``` - docker-compose up -d - ``` - -- Login to the container. - ``` - docker exec -it psql-db bash - ``` - -- Login to postgres database - ``` - psql -U postgres - ``` - -# Example -- Create database demo - ``` - CREATE DATABASE demo; - \c demo - ``` -- Create table zoo_1 and zoo_2 - ``` - CREATE TABLE zoo_1 ( - id INT PRIMARY KEY, - animal VARCHAR (100) NOT NULL - ); - CREATE TABLE zoo_2 ( - id INT PRIMARY KEY, - animal VARCHAR (100) NOT NULL - ); - ``` -- Insert row - ``` - INSERT INTO zoo_1(id, animal) - VALUES - (1, 'Lion'), - (2, 'Tiger'), - (3, 'Wolf'), - (4, 'Fox'); - - INSERT INTO zoo_2(id, animal) - VALUES - (1, 'Tiger'), - (2, 'Lion'), - (3, 'Rhino'), - (4, 'Panther'); - ``` -- Run following queries - - Inner Join - ``` - SELECT - zoo_1.id id_a, - zoo_1.animal animal_a, - zoo_2.id id_b, - zoo_2.animal animal_b - FROM - zoo_1 - INNER JOIN zoo_2 ON zoo_1.animal = zoo_2.animal; - ``` - - Left Join - ``` - SELECT - zoo_1.id, - zoo_1.animal, - zoo_2.id, - zoo_2.animal - FROM - zoo_1 - LEFT JOIN zoo_2 ON zoo_1.animal = zoo_2.animal; - ``` - - Right Join - ``` - SELECT - zoo_1.id, - zoo_1.animal, - zoo_2.id, - zoo_2.animal - FROM - zoo_1 - RIGHT JOIN zoo_2 ON zoo_1.animal = zoo_2.animal; - ``` - -> Write a query for RIGHT OUTER JOIN and FULL OUTER JOIN. \ No newline at end of file diff --git a/docs/introduction_to_webscraping.md b/docs/introduction_to_webscraping.md deleted file mode 100644 index 29b8a90..0000000 --- a/docs/introduction_to_webscraping.md +++ /dev/null @@ -1,194 +0,0 @@ -# Introduction to Webscraping. - -## Request -Introduction: -The requests module allows you to send HTTP requests using Python. - - -``` -Important Methods: - - 1)get(url,params,args) - Sends a GET request to the specified url - 2)post(url,data,json,args) - Sends a POST request to the specified url - 3)delete(url,args) - Sends a DELETE request to the specified url -``` -> Explore here: [https://www.w3schools.com/python/module_requests.asp](https://www.w3schools.com/python/module_requests.asp) - -
- -Example: Using GET -``` -import requests -response_object = requests.get('https://www.lipsum.com/') -html = response_object.content -``` -
- -## Urllib - -Introduction: -It is a Python 3 package that allows you to access, and interact with, websites using their URL’s (Uniform Resource Locator). -It has several modules for working with URL’s. - - -``` -Urllib.request - Using urllib.request, with urlopen, allows you to open the specified URL. -Urllib.error - This module is used to catch exceptions encountered from url.request -``` -> Explore here: [https://www.geeksforgeeks.org/python-urllib-module/](https://www.geeksforgeeks.org/python-urllib-module/) - - -Example: -``` -import urllib.request -request_url = urllib.request.urlopen('https://www.lipsum.com/') -print(request_url.read()) - -``` - -## Beautifulsoup - - -``` -Introduction: -Beautiful Soup is a python package which allows us to pull data out of HTML and XML documents. - -Beautiful Soup - Installation -pip install beautifulsoup4 -``` -
- -``` -Important Methods - -find(name, attrs, recursive, string, **kwargs) - - scan the entire document to find only one result. - -find_all(name, attrs, recursive, string, limit, **kwargs) - - You can use find_all to extract all the occurrences of a particular tag from the html - -``` -> Explore here: [https://www.crummy.com/software/BeautifulSoup/bs4/doc/](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) - -
-Example: - -``` -html = """ - - Beautiful Soup Example - -""" - -from bs4 import BeautifulSoup -soup = BeautifulSoup(html, 'html.parser') -print(soup.title) -``` -
- -## Regex - -Introduction -The Python module re provides full support for Perl-like regular expressions in Python - - -``` - -Important methods - -re.match(pattern, string, flags=0) - - The re.match function returns a match object on success, None on failure. We usegroup(num) or groups() function of the match object to get a matched expression - -re.search(pattern, string, flags=0) - - The search() function searches the string for a match, and returns a Match object if there is a match. - -re.findall(pattern, string, flags=0)) - - function returns a list containing all matches. - -re.sub(pattern,replace_string,string) - - The sub() function replaces the matches with the text of your choice - -Metacharacters - - [] a set of a character - . any character - ^ start with - $ end with - * zero or more occurrences - + one or more occurrences - ? zero or one occurrences - {} exactly specified number of occurrence - () capture a group -Important Special Sequences - \w Matches word characters. - \W Matches nonword characters. - \s Matches whitespace. Equivalent to [\t\n\r\f]. - \S Matches nonwhitespace - \d Matches digits. Equivalent to [0-9]. - \D Matches Nondigits -``` -
- -## Writing a script using the above packages and run it in Docker -
-Filename: web_scraping_sample.py - -``` -import requests -from bs4 import BeautifulSoup -import re -res = requests.get('https://www.lipsum.com/') -soup = BeautifulSoup(res.content, 'html5lib') # If this line causes an error, run 'pip install html5lib' or install html5lib -data=soup.find(re.compile(r'div'),attrs={'id':"Panes"}) -print(data.find("lorem")) -qes_list=[] -ans_list=[] -for row in data.findAll("div"): - qes_list.append(row.h2.text) - tempstring="" - counter=0 - for i in row.findAll("p"): - tempstring=tempstring+"\n"+i.text - ans_list.append(tempstring) -tempstring="" -for i in range(len(qes_list)): - tempstring=tempstring+"\n"+qes_list[i]+"\n"+ans_list[i]+"\n--------------------------------------------------------------------------------------------------\n\n" - print(tempstring) -``` -
- -### Creating a dockerfile -Filename: **Dockerfile** -``` -FROM python:3.10.2-alpine3.15 -# Create directories -RUN mkdir -p /root/workspace/src -COPY ./web_scraping_sample.py /root/workspace/src -# Switch to project directory -WORKDIR /root/workspace/src -# Install required packages -RUN pip install --upgrade pip -RUN pip install requests bs4 html5lib -CMD ["web_scraping_sample.py"] -ENTRYPOINT ["python"] -``` - -Build docker image -``` -docker build --no-cache --network=host ./ -t simple_python -``` -Run docker image -``` -docker run --network=host simple_python -``` \ No newline at end of file diff --git a/docs/working_with_docker_container.md b/docs/working_with_docker_container.md deleted file mode 100644 index 1e5224f..0000000 --- a/docs/working_with_docker_container.md +++ /dev/null @@ -1,70 +0,0 @@ -# Working with Docker container -
- -```Introduction -A Docker container image is a lightweight, standalone, executable package of software that includes everything needed -to run an application: code, runtime, system tools, system libraries and settings -``` -> Explore here: [https://www.docker.com/resources/what-container/#:~:text=A%20Docker%20container%20image%20is,tools%2C%20system%20libraries%20and%20settings.](https://www.docker.com/resources/what-container/#:~:text=A%20Docker%20container%20image%20is,tools%2C%20system%20libraries%20and%20settings.) - -
- -### Docker Compose - -```Introduction -Compose is a tool for defining and running multi-container Docker applications. -With Compose, you use a YAML file to configure your application’s services. -Then, with a single command, you create and start all the services from your configuration. -``` -> Explore here: [https://docs.docker.com/compose/](https://docs.docker.com/compose/) - -
- -### Exercise - - Create a new docker file. - ``` - FROM python:3.10.2-alpine3.15 - # Create directories - RUN mkdir -p /root/workspace/src - COPY ./web_scraping_sample.py /root/workspace/src - # Switch to project directory - WORKDIR /root/workspace/src - # Install required packages - RUN pip install --upgrade pip - RUN pip install requests bs4 html5lib - ``` - - Build docker image - ``` - docker build --no-cache --network=host ./ -t workshop1 - ``` - - Create a docker-compose file. - - Filename: docker-compose.yml - ``` - version: "3" - services: - python_service: - build: - context: ./ - dockerfile: Dockerfile - image: workshop1 - container_name: workshop_python_container - stdin_open: true # docker attach container_id - tty: true - ports: - - "8000:8000" - volumes: - - .:/app - ``` - -- Get the containers up. - ``` - docker-compose up -d - ``` -- Login to the container. - ``` - docker exec -it workshop_python_container sh - ``` -- Run the script for web scrapping inside the container. - ``` - python web_scraping_sample.py - ``` \ No newline at end of file diff --git a/docs/workshop1_home_work.md b/docs/workshop1_home_work.md deleted file mode 100644 index 639c7ac..0000000 --- a/docs/workshop1_home_work.md +++ /dev/null @@ -1,7 +0,0 @@ -# Workshop 1 Homework. - -### Aim: Scrape the website and store blog data in a PostgreSQL database - -- Write a Python program to Scrape the pages from [Python Blogs](https://blog.python.org/) and save the data into the DB. -- Dockerize the project. - diff --git a/prerequisites/install_docker.sh b/prerequisites/install_docker.sh deleted file mode 100755 index e9fec1d..0000000 --- a/prerequisites/install_docker.sh +++ /dev/null @@ -1,630 +0,0 @@ -#!/bin/sh -set -e -# Docker CE for Linux installation script -# -# See https://docs.docker.com/engine/install/ for the installation steps. -# -# This script is meant for quick & easy install via: -# $ curl -fsSL https://get.docker.com -o get-docker.sh -# $ sh get-docker.sh -# -# For test builds (ie. release candidates): -# $ curl -fsSL https://test.docker.com -o test-docker.sh -# $ sh test-docker.sh -# -# NOTE: Make sure to verify the contents of the script -# you downloaded matches the contents of install.sh -# located at https://github.com/docker/docker-install -# before executing. -# -# Git commit from https://github.com/docker/docker-install when -# the script was uploaded (Should only be modified by upload job): -SCRIPT_COMMIT_SHA="93d2499759296ac1f9c510605fef85052a2c32be" - -# strip "v" prefix if present -VERSION="20.10.12" - -# The channel to install from: -# * nightly -# * test -# * stable -# * edge (deprecated) -DEFAULT_CHANNEL_VALUE="stable" -if [ -z "$CHANNEL" ]; then - CHANNEL=$DEFAULT_CHANNEL_VALUE -fi - -DEFAULT_DOWNLOAD_URL="https://download.docker.com" -if [ -z "$DOWNLOAD_URL" ]; then - DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL -fi - -DEFAULT_REPO_FILE="docker-ce.repo" -if [ -z "$REPO_FILE" ]; then - REPO_FILE="$DEFAULT_REPO_FILE" -fi - -mirror='' -DRY_RUN=${DRY_RUN:-} -while [ $# -gt 0 ]; do - case "$1" in - --mirror) - mirror="$2" - shift - ;; - --dry-run) - DRY_RUN=1 - ;; - --*) - echo "Illegal option $1" - ;; - esac - shift $(( $# > 0 ? 1 : 0 )) -done - -case "$mirror" in - Aliyun) - DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce" - ;; - AzureChinaCloud) - DOWNLOAD_URL="https://mirror.azure.cn/docker-ce" - ;; -esac - -command_exists() { - command -v "$@" > /dev/null 2>&1 -} - -# version_gte checks if the version specified in $VERSION is at least -# the given CalVer (YY.MM) version. returns 0 (success) if $VERSION is either -# unset (=latest) or newer or equal than the specified version. Returns 1 (fail) -# otherwise. -# -# examples: -# -# VERSION=20.10 -# version_gte 20.10 // 0 (success) -# version_gte 19.03 // 0 (success) -# version_gte 21.10 // 1 (fail) -version_gte() { - if [ -z "$VERSION" ]; then - return 0 - fi - eval calver_compare "$VERSION" "$1" -} - -# calver_compare compares two CalVer (YY.MM) version strings. returns 0 (success) -# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch -# releases and pre-release (-alpha/-beta) are not taken into account -# -# examples: -# -# calver_compare 20.10 19.03 // 0 (success) -# calver_compare 20.10 20.10 // 0 (success) -# calver_compare 19.03 20.10 // 1 (fail) -calver_compare() ( - set +x - - yy_a="$(echo "$1" | cut -d'.' -f1)" - yy_b="$(echo "$2" | cut -d'.' -f1)" - if [ "$yy_a" -lt "$yy_b" ]; then - return 1 - fi - if [ "$yy_a" -gt "$yy_b" ]; then - return 0 - fi - mm_a="$(echo "$1" | cut -d'.' -f2)" - mm_b="$(echo "$2" | cut -d'.' -f2)" - if [ "${mm_a#0}" -lt "${mm_b#0}" ]; then - return 1 - fi - - return 0 -) - -is_dry_run() { - if [ -z "$DRY_RUN" ]; then - return 1 - else - return 0 - fi -} - -is_wsl() { - case "$(uname -r)" in - *microsoft* ) true ;; # WSL 2 - *Microsoft* ) true ;; # WSL 1 - * ) false;; - esac -} - -is_darwin() { - case "$(uname -s)" in - *darwin* ) true ;; - *Darwin* ) true ;; - * ) false;; - esac -} - -deprecation_notice() { - distro=$1 - distro_version=$2 - echo - printf "\033[91;1mDEPRECATION WARNING\033[0m\n" - printf " This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version" - echo " No updates or security fixes will be released for this distribution, and users are recommended" - echo " to upgrade to a currently maintained version of $distro." - echo - printf "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue." - echo - sleep 10 -} - -get_distribution() { - lsb_dist="" - # Every system that we officially support has /etc/os-release - if [ -r /etc/os-release ]; then - lsb_dist="$(. /etc/os-release && echo "$ID")" - fi - # Returning an empty string here should be alright since the - # case statements don't act unless you provide an actual value - echo "$lsb_dist" -} - -echo_docker_as_nonroot() { - if is_dry_run; then - return - fi - if command_exists docker && [ -e /var/run/docker.sock ]; then - ( - set -x - $sh_c 'docker version' - ) || true - fi - - # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output - echo - echo "================================================================================" - echo - if version_gte "20.10"; then - echo "To run Docker as a non-privileged user, consider setting up the" - echo "Docker daemon in rootless mode for your user:" - echo - echo " dockerd-rootless-setuptool.sh install" - echo - echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode." - echo - fi - echo - echo "To run the Docker daemon as a fully privileged service, but granting non-root" - echo "users access, refer to https://docs.docker.com/go/daemon-access/" - echo - echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent" - echo " to root access on the host. Refer to the 'Docker daemon attack surface'" - echo " documentation for details: https://docs.docker.com/go/attack-surface/" - echo - echo "================================================================================" - echo -} - -# Check if this is a forked Linux distro -check_forked() { - - # Check for lsb_release command existence, it usually exists in forked distros - if command_exists lsb_release; then - # Check if the `-u` option is supported - set +e - lsb_release -a -u > /dev/null 2>&1 - lsb_release_exit_code=$? - set -e - - # Check if the command has exited successfully, it means we're in a forked distro - if [ "$lsb_release_exit_code" = "0" ]; then - # Print info about current distro - cat <<-EOF - You're using '$lsb_dist' version '$dist_version'. - EOF - - # Get the upstream release info - lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]') - dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]') - - # Print info about upstream distro - cat <<-EOF - Upstream release is '$lsb_dist' version '$dist_version'. - EOF - else - if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then - if [ "$lsb_dist" = "osmc" ]; then - # OSMC runs Raspbian - lsb_dist=raspbian - else - # We're Debian and don't even know it! - lsb_dist=debian - fi - dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')" - case "$dist_version" in - 11) - dist_version="bullseye" - ;; - 10) - dist_version="buster" - ;; - 9) - dist_version="stretch" - ;; - 8) - dist_version="jessie" - ;; - esac - fi - fi - fi -} - -do_install() { - echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA" - - if command_exists docker; then - cat >&2 <<-'EOF' - Warning: the "docker" command appears to already exist on this system. - - If you already have Docker installed, this script can cause trouble, which is - why we're displaying this warning and provide the opportunity to cancel the - installation. - - If you installed the current Docker package using this script and are using it - again to update Docker, you can safely ignore this message. - - You may press Ctrl+C now to abort this script. - EOF - ( set -x; sleep 20 ) - fi - - user="$(id -un 2>/dev/null || true)" - - sh_c='sh -c' - if [ "$user" != 'root' ]; then - if command_exists sudo; then - sh_c='sudo -E sh -c' - elif command_exists su; then - sh_c='su -c' - else - cat >&2 <<-'EOF' - Error: this installer needs the ability to run commands as root. - We are unable to find either "sudo" or "su" available to make this happen. - EOF - exit 1 - fi - fi - - if is_dry_run; then - sh_c="echo" - fi - - # perform some very rudimentary platform detection - lsb_dist=$( get_distribution ) - lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" - - if is_wsl; then - echo - echo "WSL DETECTED: We recommend using Docker Desktop for Windows." - echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop" - echo - cat >&2 <<-'EOF' - - You may press Ctrl+C now to abort this script. - EOF - ( set -x; sleep 20 ) - fi - - case "$lsb_dist" in - - ubuntu) - if command_exists lsb_release; then - dist_version="$(lsb_release --codename | cut -f2)" - fi - if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then - dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")" - fi - ;; - - debian|raspbian) - dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')" - case "$dist_version" in - 11) - dist_version="bullseye" - ;; - 10) - dist_version="buster" - ;; - 9) - dist_version="stretch" - ;; - 8) - dist_version="jessie" - ;; - esac - ;; - - centos|rhel|sles) - if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then - dist_version="$(. /etc/os-release && echo "$VERSION_ID")" - fi - ;; - - *) - if command_exists lsb_release; then - dist_version="$(lsb_release --release | cut -f2)" - fi - if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then - dist_version="$(. /etc/os-release && echo "$VERSION_ID")" - fi - ;; - - esac - - # Check if this is a forked Linux distro - check_forked - - # Print deprecation warnings for distro versions that recently reached EOL, - # but may still be commonly used (especially LTS versions). - case "$lsb_dist.$dist_version" in - debian.stretch|debian.jessie) - deprecation_notice "$lsb_dist" "$dist_version" - ;; - raspbian.stretch|raspbian.jessie) - deprecation_notice "$lsb_dist" "$dist_version" - ;; - ubuntu.xenial|ubuntu.trusty) - deprecation_notice "$lsb_dist" "$dist_version" - ;; - fedora.*) - if [ "$dist_version" -lt 33 ]; then - deprecation_notice "$lsb_dist" "$dist_version" - fi - ;; - esac - - # Run setup for each distro accordingly - case "$lsb_dist" in - ubuntu|debian|raspbian) - pre_reqs="apt-transport-https ca-certificates curl" - if ! command -v gpg > /dev/null; then - pre_reqs="$pre_reqs gnupg" - fi - apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL" - ( - if ! is_dry_run; then - set -x - fi - $sh_c 'apt-get update -qq >/dev/null' - $sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $pre_reqs >/dev/null" - $sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg" - $sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list" - $sh_c 'apt-get update -qq >/dev/null' - ) - pkg_version="" - if [ -n "$VERSION" ]; then - if is_dry_run; then - echo "# WARNING: VERSION pinning is not supported in DRY_RUN" - else - # Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel - pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/~ce~.*/g" | sed "s/-/.*/g").*-0~$lsb_dist" - search_command="apt-cache madison 'docker-ce' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3" - pkg_version="$($sh_c "$search_command")" - echo "INFO: Searching repository for VERSION '$VERSION'" - echo "INFO: $search_command" - if [ -z "$pkg_version" ]; then - echo - echo "ERROR: '$VERSION' not found amongst apt-cache madison results" - echo - exit 1 - fi - if version_gte "18.09"; then - search_command="apt-cache madison 'docker-ce-cli' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3" - echo "INFO: $search_command" - cli_pkg_version="=$($sh_c "$search_command")" - fi - pkg_version="=$pkg_version" - fi - fi - ( - pkgs="" - if version_gte "18.09"; then - # older versions don't support a cli package - pkgs="$pkgs docker-ce-cli${cli_pkg_version%=}" - fi - if version_gte "20.10" && [ "$(uname -m)" = "x86_64" ]; then - # also install the latest version of the "docker scan" cli-plugin (only supported on x86 currently) - pkgs="$pkgs docker-scan-plugin" - fi - pkgs="$pkgs docker-ce${pkg_version%=}" - if ! is_dry_run; then - set -x - fi - $sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $pkgs >/dev/null" - if version_gte "20.10"; then - # Install docker-ce-rootless-extras without "--no-install-recommends", so as to install slirp4netns when available - $sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras${pkg_version%=} >/dev/null" - fi - ) - echo_docker_as_nonroot - exit 0 - ;; - centos|fedora|rhel) - if [ "$(uname -m)" != "s390x" ] && [ "$lsb_dist" = "rhel" ]; then - echo "Packages for RHEL are currently only available for s390x." - exit 1 - fi - yum_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE" - if ! curl -Ifs "$yum_repo" > /dev/null; then - echo "Error: Unable to curl repository file $yum_repo, is it valid?" - exit 1 - fi - if [ "$lsb_dist" = "fedora" ]; then - pkg_manager="dnf" - config_manager="dnf config-manager" - enable_channel_flag="--set-enabled" - disable_channel_flag="--set-disabled" - pre_reqs="dnf-plugins-core" - pkg_suffix="fc$dist_version" - else - pkg_manager="yum" - config_manager="yum-config-manager" - enable_channel_flag="--enable" - disable_channel_flag="--disable" - pre_reqs="yum-utils" - pkg_suffix="el" - fi - ( - if ! is_dry_run; then - set -x - fi - $sh_c "$pkg_manager install -y -q $pre_reqs" - $sh_c "$config_manager --add-repo $yum_repo" - - if [ "$CHANNEL" != "stable" ]; then - $sh_c "$config_manager $disable_channel_flag docker-ce-*" - $sh_c "$config_manager $enable_channel_flag docker-ce-$CHANNEL" - fi - $sh_c "$pkg_manager makecache" - ) - pkg_version="" - if [ -n "$VERSION" ]; then - if is_dry_run; then - echo "# WARNING: VERSION pinning is not supported in DRY_RUN" - else - pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g").*$pkg_suffix" - search_command="$pkg_manager list --showduplicates 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'" - pkg_version="$($sh_c "$search_command")" - echo "INFO: Searching repository for VERSION '$VERSION'" - echo "INFO: $search_command" - if [ -z "$pkg_version" ]; then - echo - echo "ERROR: '$VERSION' not found amongst $pkg_manager list results" - echo - exit 1 - fi - if version_gte "18.09"; then - # older versions don't support a cli package - search_command="$pkg_manager list --showduplicates 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'" - cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)" - fi - # Cut out the epoch and prefix with a '-' - pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)" - fi - fi - ( - if ! is_dry_run; then - set -x - fi - # install the correct cli version first - if [ -n "$cli_pkg_version" ]; then - $sh_c "$pkg_manager install -y -q docker-ce-cli-$cli_pkg_version" - fi - $sh_c "$pkg_manager install -y -q docker-ce$pkg_version" - if version_gte "20.10"; then - $sh_c "$pkg_manager install -y -q docker-ce-rootless-extras$pkg_version" - fi - ) - echo_docker_as_nonroot - exit 0 - ;; - sles) - if [ "$(uname -m)" != "s390x" ]; then - echo "Packages for SLES are currently only available for s390x" - exit 1 - fi - - sles_version="${dist_version##*.}" - sles_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE" - opensuse_repo="https://download.opensuse.org/repositories/security:SELinux/SLE_15_SP$sles_version/security:SELinux.repo" - if ! curl -Ifs "$sles_repo" > /dev/null; then - echo "Error: Unable to curl repository file $sles_repo, is it valid?" - exit 1 - fi - pre_reqs="ca-certificates curl libseccomp2 awk" - ( - if ! is_dry_run; then - set -x - fi - $sh_c "zypper install -y $pre_reqs" - $sh_c "zypper addrepo $sles_repo" - if ! is_dry_run; then - cat >&2 <<-'EOF' - WARNING!! - openSUSE repository (https://download.opensuse.org/repositories/security:SELinux) will be enabled now. - Do you wish to continue? - You may press Ctrl+C now to abort this script. - EOF - ( set -x; sleep 30 ) - fi - $sh_c "zypper addrepo $opensuse_repo" - $sh_c "zypper --gpg-auto-import-keys refresh" - $sh_c "zypper lr -d" - ) - pkg_version="" - if [ -n "$VERSION" ]; then - if is_dry_run; then - echo "# WARNING: VERSION pinning is not supported in DRY_RUN" - else - pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g")" - search_command="zypper search -s --match-exact 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'" - pkg_version="$($sh_c "$search_command")" - echo "INFO: Searching repository for VERSION '$VERSION'" - echo "INFO: $search_command" - if [ -z "$pkg_version" ]; then - echo - echo "ERROR: '$VERSION' not found amongst zypper list results" - echo - exit 1 - fi - search_command="zypper search -s --match-exact 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'" - # It's okay for cli_pkg_version to be blank, since older versions don't support a cli package - cli_pkg_version="$($sh_c "$search_command")" - pkg_version="-$pkg_version" - - search_command="zypper search -s --match-exact 'docker-ce-rootless-extras' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'" - rootless_pkg_version="$($sh_c "$search_command")" - rootless_pkg_version="-$rootless_pkg_version" - fi - fi - ( - if ! is_dry_run; then - set -x - fi - # install the correct cli version first - if [ -n "$cli_pkg_version" ]; then - $sh_c "zypper install -y docker-ce-cli-$cli_pkg_version" - fi - $sh_c "zypper install -y docker-ce$pkg_version" - if version_gte "20.10"; then - $sh_c "zypper install -y docker-ce-rootless-extras$rootless_pkg_version" - fi - ) - echo_docker_as_nonroot - exit 0 - ;; - *) - if [ -z "$lsb_dist" ]; then - if is_darwin; then - echo - echo "ERROR: Unsupported operating system 'macOS'" - echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop" - echo - exit 1 - fi - fi - echo - echo "ERROR: Unsupported distribution '$lsb_dist'" - echo - exit 1 - ;; - esac - exit 1 -} - -# wrapped up in a function so that we have some protection against only getting -# half the file during "curl | sh" -do_install \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 34c449f..b897dd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -psycopg2==2.9.3 -bs4 -urllib2 requests -html5lib==1.1 \ No newline at end of file +beautifulsoup4 +psycopg2-binary diff --git a/web_scraping.py b/web_scraping.py new file mode 100644 index 0000000..0888f91 --- /dev/null +++ b/web_scraping.py @@ -0,0 +1,99 @@ +import requests +from bs4 import BeautifulSoup +import psycopg2 +import os +import logging + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +def fetch_blog_posts(base_url="https://blog.python.org/"): + all_posts = [] + + try: + while base_url: + response = requests.get(base_url) + response.raise_for_status() + soup = BeautifulSoup(response.content, 'html.parser') + posts = soup.find_all('div', class_='date-outer') + + for post in posts: + title_tag = post.find('h3', class_='post-title') + date_tag = post.find('h2', class_='date-header') + content_tag = post.find('div', class_='post-body') + author_tag = post.find('span', class_='fn') + + if title_tag and date_tag and content_tag: + title = title_tag.get_text(strip=True) + date = date_tag.get_text(strip=True) + content = content_tag.get_text(strip=True) + else: + logging.warning("Skipping incomplete post") + continue + + author = author_tag.get_text(strip=True) if author_tag else 'Unknown' + all_posts.append({ + 'title': title, + 'date': date, + 'author': author, + 'content': content + }) + + older_posts_link = soup.find('a', {'class': 'blog-pager-older-link'}) + base_url = older_posts_link['href'] if older_posts_link else None + + except requests.exceptions.RequestException as e: + logging.error(f"Failed to retrieve page {base_url}. Error: {str(e)}") + + return all_posts + +def save_posts_to_db(posts): + # PostgreSQL connection details + DB_NAME = os.getenv("DB_NAME", "postgres") + DB_USER = os.getenv("DB_USER", "postgres") + DB_PASSWORD = os.getenv("DB_PASSWORD", "postgres") + DB_HOST = os.getenv("DB_HOST", "db") + DB_PORT = os.getenv("DB_PORT", "5432") + + try: + conn = psycopg2.connect( + dbname=DB_NAME, + user=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT + ) + + with conn.cursor() as cur: + cur.execute(""" + CREATE TABLE IF NOT EXISTS scrapped_contents ( + id SERIAL PRIMARY KEY, + date TEXT, + title TEXT, + author TEXT, + content TEXT + ); + """) + + for post in posts: + cur.execute( + "INSERT INTO scrapped_contents (date, title, author, content) VALUES (%s, %s, %s, %s)", + (post['date'], post['title'], post['author'], post['content']) + ) + + conn.commit() + logging.info("Data has been successfully written to the PostgreSQL database") + + except psycopg2.Error as e: + logging.error(f"Error inserting data into PostgreSQL: {e}") + + finally: + if conn: + conn.close() + +def main(): + blog_posts = fetch_blog_posts() + if blog_posts: + save_posts_to_db(blog_posts) + +if __name__ == "__main__": + main() diff --git a/web_scraping_sample.py b/web_scraping_sample.py deleted file mode 100644 index c9ca142..0000000 --- a/web_scraping_sample.py +++ /dev/null @@ -1,31 +0,0 @@ -import requests -from bs4 import BeautifulSoup -import re -import psycopg2 - -# Create connection to database -conn = psycopg2.connect( - host="postgres_service", - database="LipsumGenerator", - user="postgres", - password="admin") -cursor = conn.cursor() - -res = requests.get('https://www.lipsum.com/') -soup = BeautifulSoup(res.content, 'html5lib') # If this line causes an error, run 'pip install html5lib' or install html5lib -data = soup.find(re.compile(r'div'), attrs={'id': "Panes"}) -print(data.find("lorem")) - -question_list = [] -answer_list = [] -for row in data.findAll("div"): - question_list.append(row.h2.text) - temp_string = "" - counter=0 - for i in row.findAll("p"): - temp_string = temp_string + "\n" + i.text - answer_list.append(temp_string) -file = open("qn_ans_ans", "w") - -for i in range(len(question_list)): - cursor.execute("insert into qn_ans values(%s,%s)", (question_list[i], answer_list[i])) From 7bf5bb42cb35f38ebe144451ae4c2d131237e3c2 Mon Sep 17 00:00:00 2001 From: PreethamShastry Date: Tue, 9 Jul 2024 15:19:13 +0530 Subject: [PATCH 2/2] first session commit2 --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 9dd0a0e..f37a643 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,17 @@ One Day workshop on understanding Docker, Web Scrapping, Regular Expressions, Po | 04:00 - 04:30 | [`Introduction to Github`](/docs/introduction_to_git_commands.md) | 04:30 - 04:45 | `Q & A` | 04:45 - 05:00 | [`Wrapping Up`](/docs/workshop1_home_work.md) + + +The project focuses on extracting specific information—date, title, author, and content—from the "https://blog.python.org/" website using Beautiful Soup, a web scraping library in Python. The extracted data is stored in a PostgreSQL database for structured storage. To streamline the deployment and ensure consistency across different environments, the entire setup is containerized using Docker. Docker Compose is to manage multi-container Docker applications, making it easier to define and run the PostgreSQL database and the web scraping script within isolated containers. This approach not only simplifies the development process but also enhances scalability and maintainability, allowing for seamless updates and modifications. By containerizing the application, the project ensures a portable and reproducible environment, reducing the complexities associated with dependency management and environment configuration. + +steps to follow: +1. To build the image "sudo docker-compose build" + +2. To run th container "sudo docker-compose up" + +3. To login into postgres "sudo docker exec -it psql -U postgres -d postgres" + +4. SELECT * FROM scrapped_contents + +5. To stop the container "sudo docker-compose down" \ No newline at end of file