-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcrawler.py
More file actions
193 lines (137 loc) · 5.58 KB
/
Copy pathcrawler.py
File metadata and controls
193 lines (137 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Little program to crawl some ToR pages and demonstrate the usage of the Stem API to
connect ToR from within Python.
Made by Foo-Manroot
Last change on Feb 04, 2018
"""
import logging \
, sys \
, re \
, io \
, pycurl
from urllib.parse import urlparse \
, urljoin
# To parse HTML
from bs4 import BeautifulSoup
class Crawler ():
"""
Spider to extract all links on the provided URL, mapping an entire webpage along
with the linked webs.
The process is repeated in every webpage found until no more links are left.
"""
def __init__ (self, initial_url, proxy_port = None, max_depth = 2):
"""
Constructor
Args:
initial_url -> Starting URL from which the spider will begijn crawling
proxy_port (OPTIONAL) -> Port where the local proxy is listening on. If it's
None, no proxy will be used
max_depth (OPTIONAL) -> Maximum recursion depth
"""
self.initial_url = initial_url
self.max_depth = max_depth
self.user_agent = "AmigaVoyager/3.2 (AmigaOS/MC680x0)"
self.proxy_port = proxy_port
# List to avoid requesting the same URL twice
self.crawled_urls = set ()
# Seconds to wait a connection before giving up
self.timeout = 360
logging.info ("Crawler initialized to start from: " + self.initial_url)
def start (self):
"""
Starts the crawler
"""
self.scrape_page (self.initial_url)
def process_page (self, url, parsed):
"""
Extracts the information from the webpage, and logs it
Args:
url -> URL of the webpage
parsed -> Object returned by BeautifulSoup with the parsed HTML
"""
# Extracts title and URL of the webpage
title = parsed.title
title = title.text if title else "-"
description = parsed.find ("meta", attrs = {"name": "description"})
description = description ["content"] if description else "-"
# Substitutes " with '
msg = ("\"" + re.sub ("\"", "'", url)
+ "\",\"" + re.sub ("\"", "'", title.strip ("\r\n\t"))
+ "\",\"" + re.sub ("\"", "'", description.strip (" \r\n\t"))
+ "\""
)
print (" => " + msg)
sys.stdout.flush ()
def urlopen (self, url):
"""
Uses pycurl to fetch a site using the local proxy on the SOCKS_PORT.
"""
output = io.BytesIO ()
query = pycurl.Curl ()
query.setopt (pycurl.URL, url)
query.setopt (pycurl.USERAGENT, self.user_agent)
if self.proxy_port:
query.setopt (pycurl.PROXY, '127.0.0.1')
query.setopt (pycurl.PROXYPORT, self.proxy_port)
query.setopt (pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5_HOSTNAME)
# Follow redirects
query.setopt (pycurl.FOLLOWLOCATION, 1)
query.setopt (pycurl.CONNECTTIMEOUT, self.timeout)
query.setopt (pycurl.WRITEFUNCTION, output.write)
query.perform ()
return output.getvalue ()
def scrape_page (self, url, current_depth = 0):
"""
Scrapes the given webpage, looking for links to other pages recursively.
The strategy used is depth-first search.
Args:
url -> URL of the webpage to get links from
current_depth (OPTIONAL) -> Current depth of the recursion
"""
logging.info ("Processing " + url)
logging.info ("Current depth: " + str (current_depth))
# Checks the maximum depth
if current_depth > self.max_depth:
logging.debug ("Max depth reached when processing " + url)
return
try:
html = self.urlopen (url)
except Exception as e:
logging.error ("Skipping processing of " + url + " -> " + str (e))
return
parsed = BeautifulSoup (html, "html5lib")
# Processes the webpage info
self.process_page (url, parsed)
# Gets all anchors
tag = parsed.find ("a", href = True)
while tag is not None:
# Gets the destination and also scrapes that page
href = tag ["href"]
# Updates the value of 'tag' to be the next anchor
tag = tag.find_next ("a", href = True)
logging.debug ("Extracted href: " + href)
parsed_url = urlparse (href)
# If it was a relative link, constructs
if not parsed_url.netloc and not parsed_url.scheme:
# An empty 'netloc' attribute means that it was a relative path
base_url = urlparse (url)
# Gets only the path and query of the resource, without fragment
# (the final #<id>)
rel_path = parsed_url.path.strip ("/")
if parsed_url.query:
rel_path += "?" + parsed_url.query
new_url = base_url.scheme + "://" + base_url.netloc + "/" + rel_path
elif parsed_url.scheme == "http" or parsed_url.scheme == "https":
new_url = href
else:
# Skips the link
logging.info ("Skipping processing of " + href)
continue
# Also, discards the already crawled pages
if new_url == url or new_url in self.crawled_urls:
continue
self.crawled_urls.add (new_url)
# Scrapes the new page, increasing the recursiong depth
self.scrape_page (new_url, current_depth + 1)