Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.

Commit cbc07e7

Browse files
committed
2026 update
Updates to readme documentation Updates to application in crawl.py Selenium module upgrade Compatibility fix for running this Django 4.0 app with Python 13+ (created cgi.py) Upgraded Web Crawling Pipeline (CI)
1 parent 9ada36a commit cbc07e7

9 files changed

Lines changed: 119 additions & 95 deletions

File tree

‎.env.example‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CORS_ALLOWED_ORIGINS='http://localhost'
55
TIME_ZONE='Europe/London'
66
APP_ENV=development
77
SIGNING_KEY=2de5c7b55db0e2bcbaac7a135e7b0cf340da8dba93f8c6d4f5ef0934887c834b
8+
CONNECTION=sqlite
89
MARIADB_DATABASE=python_selenium
910
MARIADB_USER=root
1011
MARIADB_PASSWORD=

‎.env.testing‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CORS_ALLOWED_ORIGINS='http://localhost'
55
TIME_ZONE='Europe/London'
66
APP_ENV=testing
77
SIGNING_KEY=2de5c7b55db0e2bcbaac7a135e7b0cf340da8dba93f8c6d4f5ef0934887c834b
8+
CONNECTION=mysql
89
MARIADB_DATABASE=python_selenium
910
MARIADB_USER=root
1011
MARIADB_PASSWORD=

‎.github/workflows/build.yml‎

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ jobs:
1414
strategy:
1515
max-parallel: 4
1616
matrix:
17-
python-version: [3.11]
17+
python-version: [3.13]
1818

1919
services:
2020
mysql:
21-
image: mysql:5.7
21+
image: mysql:8.0
2222
env:
2323
MYSQL_ROOT_PASSWORD: 'secret'
2424
MYSQL_DATABASE: 'python_selenium'
@@ -27,35 +27,23 @@ jobs:
2727
ports:
2828
- 3306/tcp
2929
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
30-
redis:
31-
image: redis
32-
options: >-
33-
--health-cmd "redis-cli ping"
34-
--health-interval 10s
35-
--health-timeout 5s
36-
--health-retries 5
37-
ports:
38-
- 6379:6379
3930

4031
steps:
41-
- uses: actions/checkout@v3
32+
- uses: actions/checkout@v7
4233
- name: Set up Python ${{ matrix.python-version }}
43-
uses: actions/setup-python@v3
34+
uses: actions/setup-python@v7
4435
with:
4536
python-version: ${{ matrix.python-version }}
46-
- uses: actions/checkout@v2
47-
- uses: nanasess/setup-chromedriver@v2
37+
- uses: nanasess/setup-chromedriver@v3
4838
- run: |
4939
export DISPLAY=:99
5040
chromedriver --url-base=/wd/hub &
41+
# Xvfb is case-sensitive
5142
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
52-
5343
- name: Create environment file
5444
run: cp .env.testing .env
5545
- name: Install Dependencies
56-
run: |
57-
python -m pip install --upgrade pip
58-
pip install -r requirements.txt
46+
run: pip install -rvq --no-input requirements.txt
5947
- name: See your Python code do web browsing on your screen with GUI
6048
run: python manage.py crawl
6149
- name: Upload Screenshots

‎.gitignore‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,4 +284,5 @@ db.sqlite3
284284
migrations/
285285
nohup.out
286286
screenshots/
287-
tmp
287+
tmp
288+
/env

‎app/settings.py‎

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
import os
1616

1717
env = environ.Env(
18-
DEBUG=(bool,False)
18+
DEBUG=(bool,False),
19+
CONNECTION=(str, None),
1920
)
2021

2122
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -38,11 +39,7 @@
3839
# SECURITY WARNING: keep the secret key used in production secret!
3940
SECRET_KEY = env('SECRET_KEY')
4041

41-
SELENIUM_HEADLESS = env('SELENIUM_HEADLESS')
42-
if SELENIUM_HEADLESS == 'True':
43-
SELENIUM_HEADLESS = True
44-
else:
45-
SELENIUM_HEADLESS = False
42+
SELENIUM_HEADLESS = env.bool('SELENIUM_HEADLESS', default=False)
4643

4744
# SECURITY WARNING: don't run with debug turned on in production!
4845
DEBUG = env('DEBUG')
@@ -227,17 +224,21 @@
227224
# Database
228225
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
229226

230-
DATABASES = {
231-
'default': {
232-
'ENGINE': 'django.db.backends.mysql',
233-
'NAME': env('MARIADB_DATABASE'),
234-
'USER': env('MARIADB_USER'),
235-
'PASSWORD': env('MARIADB_PASSWORD'),
236-
'HOST': env('MARIADB_HOST'),
237-
'PORT': env('MARIADB_PORT'),
227+
if env.str("CONNECTION") is None or env.str("CONNECTION") == "sqlite":
228+
DATABASES = {
229+
"default": {
230+
"ENGINE": "django.db.backends.sqlite3",
231+
"NAME": BASE_DIR / "db.sqlite3",
232+
}
233+
}
234+
else:
235+
DATABASES = {
236+
"default": {
237+
"ENGINE": "django.db.backends.mysql",
238+
"NAME": env("MARIADB_DATABASE"),
239+
"USER": env("MARIADB_USER"),
240+
"PASSWORD": env("MARIADB_PASSWORD"),
241+
"HOST": env("MARIADB_HOST"),
242+
"PORT": env("MARIADB_PORT"),
243+
}
238244
}
239-
}
240-
241-
if 'production' == APP_ENV:
242-
import django_on_heroku
243-
django_on_heroku.settings(locals())

‎cgi.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Compatibility shim for Python 3.13+ where stdlib cgi was removed.
2+
3+
Only the API used by this project stack (Django 4.0.x) is implemented.
4+
"""
5+
6+
from email.message import Message
7+
8+
9+
def parse_header(line):
10+
"""Parse a Content-type like header.
11+
12+
Returns (main_value, params_dict), matching the old cgi.parse_header shape.
13+
"""
14+
message = Message()
15+
message["content-type"] = line
16+
17+
main_value = message.get_content_type()
18+
params = {}
19+
20+
# Skip the first item, which repeats the content type itself.
21+
for key, value in message.get_params()[1:]:
22+
params[key] = value
23+
24+
return main_value, params

‎readme.md‎

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66

77
* [Important note:](#note)
88

9+
* [Requirements](#requirements)
10+
911
* [Installation](#installation)
1012

13+
* [Configure This Project](#configure-this-project)
14+
15+
* [Run Your Selenium Server Jar File](#run-your-selenium-server-jar-file)
16+
1117
* [Usage](#usage)
1218

1319
* [Using Docker](#using-docker)
@@ -30,54 +36,55 @@
3036

3137
## Important note: <a name="note"></a>
3238

33-
Before you try to scrape any website, go through its robots.txt file. You can access it via `domainname/robots.txt`. There, you will see a list of pages allowed and disallowed for scraping. You should not violate any terms of service of any website you scrape.
39+
Before you try to scrape any website, go through its robots.txt file. You can access it via `domainname/robots.txt`. There, you will see a list of pages allowed and disallowed for scraping. You should not violate any terms of service of any website you scrape.
40+
41+
## Requirements
42+
43+
* [Tested using Python 3.13](https://www.python.org)
44+
* [Chromedriver](https://developer.chrome.com/docs/chromedriver)
45+
* [Selenium Server JAR file](https://www.selenium.dev/documentation/grid/getting_started)
3446

3547
## Installation
3648

49+
#### Configure This Project
50+
3751
```bash
3852
cp .env.example .env
39-
python3 -m venv env && \
53+
python -m venv env && \
4054
source env/bin/activate
4155
pip install -r requirements.txt
42-
python3 manage.py migrate
56+
python manage.py makemigrations
57+
python manage.py migrate
4358
```
4459

45-
#### Add chromedriver to Path
60+
#### Run Your Selenium Server JAR File
4661

47-
Make sure Chromedriver is installed and added to your environment Path.
62+
Locate where you downloaded your Selenium Server JAR file in the [requirements](#requirements) step and run the following.
4863

4964
```bash
50-
# chromedriver_mac64
51-
# chromedriver_win32
52-
# See https://chromedriver.storage.googleapis.com
53-
# for drivers list.
54-
wget https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip
55-
unzip chromedriver_linux64.zip
56-
sudo mv chromedriver /usr/local/bin/chromedriver
57-
chromedriver --version
65+
java -jar selenium-server-[version].jar standalone --override-max-sessions true --max-sessions 10
5866
```
5967

68+
[CLI options in the Selenium Grid](https://www.selenium.dev/documentation/grid/configuration/cli_options/).
69+
6070
## Usage
6171

6272
[XPath cheat sheet](https://devhints.io/xpath).
6373

64-
Update the command at [crawl.py](https://github.com/kkamara/python-selenium/blob/main/seleniumpy/management/commands/crawl.py)
74+
Update the command at [crawl.py](https://github.com/kkamara/python-selenium/blob/main/seleniumpy/management/commands/crawl.py) to perform your instructions in web scraping.
6575

6676
```bash
67-
alias py="python3"
68-
py manage.py crawl
77+
python manage.py crawl
6978
```
7079

71-
If you still need help installing and running the app check out the readme at https://github.com/kkamara/python-react-boilerplate which is the base system for this python-selenium app.
72-
7380
## Using Docker?
7481

7582
```bash
7683
alias compose='docker-compose -f local.yml'
7784
compose build
7885
compose up
7986
# Automated runs with Docker:
80-
# compose up --build -d && python3 manage.py crawl
87+
# compose up --build -d && python manage.py crawl
8188
```
8289

8390
## iPython Django Shell

‎requirements.txt‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
selenium==3.141.0
1+
selenium==4.45.0
22
asgiref==3.5.0
3-
certifi==2021.10.8
43
chardet==4.0.0
54
charset-normalizer==2.0.12
65
coreapi==2.3.3
@@ -31,13 +30,13 @@ PyJWT==1.7.1
3130
PyMySQL==1.0.2
3231
python3-openid==3.2.0
3332
pytz==2022.1
34-
requests==2.27.1
33+
requests==2.34.2
3534
requests-oauthlib==1.3.1
3635
six==1.16.0
3736
social-auth-app-django==4.0.0
3837
social-auth-core==4.0.2
3938
sqlparse==0.4.2
4039
uritemplate==4.1.1
41-
urllib3==1.26.9
4240
whitenoise==6.0.0
4341
zipp==3.8.0
42+
ipython~=9.14.1
Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,72 @@
11
from django.core.management.base import BaseCommand, CommandError
22
from django.conf import settings
33

4-
import os
5-
import json
64
import time
75
import logging
86
import random
97

108
from selenium import webdriver
119
from selenium.webdriver.common.by import By
12-
from selenium.webdriver.common.keys import Keys
10+
1311

1412
class Command(BaseCommand):
15-
help = 'help text'
13+
help = "help text"
1614

1715
def handle(self, *args, **options):
18-
'''
16+
"""
1917
The Selenium Python Documentation:
2018
https://www.selenium.dev/documentation/
21-
'''
19+
"""
2220
try:
2321
chrome_options = webdriver.ChromeOptions()
24-
chrome_options.add_argument('--disable-extensions')
25-
chrome_options.add_argument('--disable-gpu')
26-
chrome_options.add_argument('--no-sandbox')
27-
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
22+
chrome_options.add_argument("--disable-extensions")
23+
chrome_options.add_argument("--disable-gpu")
24+
chrome_options.add_argument("--no-sandbox")
25+
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
2826
user_agent = self.get_random_user_agent()
29-
logging.info(f'Using User Agent : '+user_agent)
30-
chrome_options.add_argument('--user-agent='+user_agent)
27+
logging.info(f"Using User Agent : {user_agent}")
28+
chrome_options.add_argument(f"--user-agent={user_agent}")
3129
# Use SELENIUM_HEADLESS in .env to remove GUI.
32-
if settings.SELENIUM_HEADLESS == True or settings.APP_ENV == 'testing':
33-
chrome_options.add_argument('--headless')
34-
if settings.APP_ENV == 'testing':
35-
chrome_options.add_argument('--disable-dev-shm-usage')
36-
browser = webdriver.Chrome(options=chrome_options)
37-
38-
browser.get('https://www.kelvinkamara.com')
30+
if settings.SELENIUM_HEADLESS or settings.APP_ENV == "testing":
31+
chrome_options.add_argument("--headless")
32+
if settings.APP_ENV == "testing":
33+
chrome_options.add_argument("--disable-dev-shm-usage")
34+
browser = webdriver.Remote(
35+
command_executor="http://127.0.0.1:4444/wd/hub", options=chrome_options
36+
)
3937

40-
self.screenshot(browser, name='debug')
38+
browser.get("https://www.kelvinkamara.com")
4139

42-
if 'Thisisnotinpagesource.' in browser.page_source:
43-
raise RuntimeError('We were detected.')
44-
time.sleep(1)
40+
self.screenshot(browser, name="debug")
4541

46-
browser.find_element(
47-
By.XPATH, "//a[@id='contact-me-link']").click()
48-
42+
if "Thisisnotinpagesource." in browser.page_source:
43+
raise RuntimeError("We were detected.")
4944
time.sleep(1)
5045

46+
browser.find_element(By.XPATH, "//a[@id='contact-me-link']").click()
47+
48+
time.sleep(2)
49+
5150
browser.quit()
52-
self.stdout.write(self.style.SUCCESS('Success'))
51+
self.stdout.write(self.style.SUCCESS("Success"))
5352
except Exception as e:
5453
try:
5554
browser.quit()
56-
except:
55+
except Exception:
5756
pass
5857
raise CommandError(str(e))
5958

60-
def screenshot(self, browser, el=None, name='example'):
61-
browser.save_screenshot(f'./screenshots/{name}.png')
59+
def screenshot(self, browser, el=None, name="example"):
60+
browser.save_screenshot(f"./screenshots/{name}.png")
6261
if el:
63-
el_text = browser.execute_script('return arguments[0].innerText', el)
62+
el_text = browser.execute_script("return arguments[0].innerText", el)
6463
logging.info(el_text)
6564

6665
def get_random_user_agent(self):
6766
user_agents = [
68-
'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.1+ (KHTML, like Gecko) Version/10.0.0.1337 Mobile Safari/537.1+', 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+', 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0.187 Mobile Safari/534.11+']
67+
"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.1+ (KHTML, like Gecko) Version/10.0.0.1337 Mobile Safari/537.1+",
68+
"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+",
69+
"Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0.187 Mobile Safari/534.11+",
70+
]
6971
key = random.randint(0, 2)
70-
return user_agents[key]
72+
return user_agents[key]

0 commit comments

Comments
 (0)