偶然间学习实验楼课程, 看到的, 想到自己刚刚出来找工作的时候, 面试过一家公司让自己实现线程池, 并利用实现的线程池进行多线程爬取, 当时水平太菜(现在也很菜, 不过比那个时候好多了). 这里记录下, 当时没有理解的是queue.Queue().task_done(), 现在想想就是这个线程的任务处理完了, 通知queue.Queue(), 让他知道这个处理完了, 别的线程调用queue.Queue().get()时, 不会有这个处理过的element出现, 让其他线程处理.
这里将实验楼的代码贴过来, 权当留念了. 不过代码有好多可以改进或者说有更好的方式实现. 比如:
- 使用requests而不是socket来爬取网站内容
- 使用BeautifulSoup或者xpath来解析爬取的网页内容中的链接信息
from queue import Queue
from threading import Thread, Lock
import urllib.parse
import socket
import re
import time
seen_urls = set(['/'])
lock = Lock()
class Fetcher(Thread):
def __init__(self, tasks):
Thread.__init__(self)
self.tasks = tasks
self.daemon = True
self.start()
def run(self):
while True:
url = self.tasks.get()
print(url)
sock = socket.socket()
sock.connect(('localhost', 3000))
get = 'GET {} HTTP/1.0\r\nHost: localhost\r\n\r\n'.format(url)
sock.send(get.encode('ascii'))
response = b''
chunk = sock.recv(4096)
while chunk:
response += chunk
chunk = sock.recv(4096)
links = self.parse_links(url, response)
lock.acquire()
for link in links.difference(seen_urls):
self.tasks.put(link)
seen_urls.update(links)
lock.release()
self.tasks.task_done()
def parse_links(self, fetched_url, response):
if not response:
print('error: {}'.format(fetched_url))
return set()
if not self._is_html(response):
return set()
urls = set(re.findall(r'''(?i)href=["']?([^\s"'<>]+)''',
self.body(response)))
links = set()
for url in urls:
normalized = urllib.parse.urljoin(fetched_url, url)
parts = urllib.parse.urlparse(normalized)
if parts.scheme not in ('', 'http', 'https'):
continue
host, port = urllib.parse.splitport(parts.netloc)
if host and host.lower() not in ('localhost'):
continue
defragmented, frag = urllib.parse.urldefrag(parts.path)
links.add(defragmented)
return links
def body(self, response):
body = response.split(b'\r\n\r\n', 1)[1]
return body.decode('utf-8')
def _is_html(self, response):
head, body = response.split(b'\r\n\r\n', 1)
headers = dict(h.split(': ') for h in head.decode().split('\r\n')[1:])
return headers.get('Content-Type', '').startswith('text/html')
class ThreadPool:
def __init__(self, num_threads):
self.tasks = Queue()
for _ in range(num_threads):
Fetcher(self.tasks)
def add_task(self, url):
self.tasks.put(url)
def wait_completion(self):
self.tasks.join()
if __name__ == '__main__':
start = time.time()
pool = ThreadPool(4)
pool.add_task("/")
pool.wait_completion()
print('{} URLs fetched in {:.1f} seconds'.format(len(seen_urls),time.time() - start))
总结:
- 线程池就是一个包含多个线程的容器, 内部有多个线程, 现成一直处于工作状态.
- 线程处于工作状态的意思是说: 当任务队列中有任务时, 线程从任务队列中取得任务, 然后进行处理, 并将任务结果以合理的方式再次反馈到任务队列中; 当任务队列中没有任务时, 则阻塞, 等待其他线程向任务队列添加任务或者通过调用线程池的add_task添加初始任务.
- 调用任务队列的join方法实现阻塞等待, 当所有任务都已经执行完成后(任务队列为空, 并且所有现成都处于阻塞状态), 输出统计信息.
内容都是个人理解, 如有出入, 请赐教.