Files
webcrawler/MultiThreaded.py

193 lines
6.8 KiB
Python

__author__ = "Aman Tahiliani"
import requests
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin
import argparse
import multiprocessing
import time
import matplotlib.pyplot as plt
import pandas as pd
import json
class WebCrawler:
def __init__(self, seed_url):
manager = multiprocessing.Manager()
self.url_queue = manager.list()
self.url_queue.append(seed_url)
self.visited_urls = manager.list()
self.counter = manager.Value("i", 0)
self.h1_word_frequency = manager.dict()
self.url_queue_lock = manager.Lock()
self.visited_lock = manager.Lock()
self.freq_lock = manager.Lock()
def extract_page_info(self, url):
# print(f"Extracting information from Url {url}")
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE"
}
try:
response = requests.get(url, headers=headers)
except:
return
if response.status_code != 200:
raise Exception(response.text)
soup = bs(response.content, "html.parser")
all_page_links = soup.find_all("a", href=True)
# print(f"Found {len(set(all_page_links))} links in the current page")
for page_link in all_page_links:
with self.url_queue_lock:
self.url_queue.append(urljoin(url, page_link["href"]))
filtered_elements = soup.find_all(
lambda tag: tag.name in ["h1", "h2", "h3"]
and not tag.find_parents(["header", "footer", "nav"])
)
for tags in filtered_elements:
current_sentence = tags.text
for current_word in current_sentence.split():
current_word = current_word.lower()
with self.freq_lock:
current_word_dict = self.h1_word_frequency.get(
current_word, {url: 0}
)
current_word_dict[url] = current_word_dict.get(url, 0) + 1
self.h1_word_frequency[current_word] = current_word_dict
return True
except Exception as e:
print(f"Exception occurred while crawling page {url}. Exception -> {e}")
return False
def process_url(self, new_url):
shouldVisit = False
with self.visited_lock:
if new_url not in self.visited_urls:
shouldVisit = True
self.visited_urls.append(new_url)
self.counter.value += 1
print(f"Page Number {self.counter.value}")
if shouldVisit:
visited = self.extract_page_info(new_url)
def crawler(self, pages_to_parse):
crawl_start_time = time.time()
pages_per_second_list = []
total_pages_processed = 0
with multiprocessing.Pool() as pool:
while True:
with self.url_queue_lock:
if not self.url_queue:
break
chunk_size = min(len(self.url_queue), multiprocessing.cpu_count())
chunks = [self.url_queue.pop(0) for chunk in range(chunk_size)]
batch_start_time = time.time()
pool.starmap(self.process_url, [(url,) for url in chunks])
batch_end_time = time.time()
pages_processed = len(chunks)
total_pages_processed += pages_processed
time_taken = batch_end_time - batch_start_time
pages_per_second = pages_processed / time_taken
for _ in range(pages_processed):
pages_per_second_list.append(pages_per_second)
with self.visited_lock:
counter_val = self.counter.value
if counter_val >= pages_to_parse:
break
crawl_end_time = time.time()
total_crawl_time = crawl_end_time - crawl_start_time
total_pages_crawled = self.counter.value
pages_per_minute = total_pages_crawled / (total_crawl_time / 60)
print("Done Parsing Pages")
print(f"Pages Parsed {self.counter.value}/{pages_to_parse}")
print(f"Links left to parse {len(self.url_queue)}")
print(f"Words found in h1 {len(self.h1_word_frequency)}")
with open("Keywords_Output.json", "w") as json_file:
json.dump(dict(self.h1_word_frequency), json_file, indent=4)
print(
f"Number of pages crawled vs left to be crawled -> {self.counter.value}/{len(self.url_queue)} = {self.counter.value / len(self.url_queue)} "
)
plt.plot(range(1, total_pages_processed + 1), pages_per_second_list)
plt.xlabel("Pages")
plt.ylabel("Pages per Second")
plt.title("Pages per Second for each page")
plt.savefig("pages_per_second.png")
speed_table = pd.DataFrame(
{
"Total Pages Crawled": [total_pages_crawled],
"Total Crawl Time (seconds)": [total_crawl_time],
"Pages per Minute": [pages_per_minute],
}
)
print("\nCrawl Speed in terms of Pages per Minute:")
print(speed_table)
fig, ax = plt.subplots(figsize=(8, 4))
ax.axis("tight")
ax.axis("off")
ax.table(
cellText=speed_table.values,
colLabels=speed_table.columns,
cellLoc="center",
loc="center",
)
plt.savefig("crawlspeed.png")
crawl_ratio_table = pd.DataFrame(
{
"Total Pages Crawled": [total_pages_crawled],
"Pages Left to Crawl": [len(self.url_queue)],
"Crawl Ratio": [{self.counter.value / len(self.url_queue)}],
}
)
fig, ax = plt.subplots(figsize=(8, 4))
ax.axis("tight")
ax.axis("off")
ax.table(
cellText=crawl_ratio_table.values,
colLabels=crawl_ratio_table.columns,
cellLoc="center",
loc="center",
)
plt.savefig("crawl_ratio_table.png")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Crawler to crawl forward from a seed URL"
)
parser.add_argument("seed_url", help="URL to start the crawling from")
parser.add_argument(
"--pages_to_parse",
help="Upper limit of how many pages that need to be parsed",
default=1000,
)
args = parser.parse_args()
seed_url = args.seed_url
pages_to_parse = int(args.pages_to_parse)
web_crawler = WebCrawler(seed_url)
print(f"Seed Url: {seed_url} \nNumber of Pages to Parse {pages_to_parse}")
print("Initiating Crawler....")
web_crawler.crawler(pages_to_parse)