我有一个由我自己制作的小型python程序,它为一些网站提供了一些价格。我正在使用beautifulsoup 4和python线程模块。
问题是我不知道如何“控制”线程。从代码中可以看出,我创建了线程类的子类(类似于consumer,producer)。在一个班级中,我从页面中获取链接,而在另一个班级中,我正在使用BS4在html中查找一些类并写入主文件。
当我启动脚本时,我通常从线程1开始。我正在抓取网站上的每个链接,获取名称和文章价格。对于每个链接,我都在制作线程。由于网站有很多链接(大约3000个),经过一段时间后,我有那么多线程正在杀死我的电脑。 Python.exe大约2 GB,我必须杀死该程序。
这是我第四天试图寻找解决方案......请.... :)
如果我说得对:setDaemon(true) - 程序在执行后杀死它们.join()等待完成线程。
我完全是编程的初学者,也知道代码有点乱。欢迎任何建议。
不要担心最后几个试块。它只是为了好玩。
谢谢!
import threading
import csv
import urllib2
import time
from bs4 import BeautifulSoup
import re
import Queue
httpLink = "WWW.SOMEWEBSITE.COM"
fn = 'J:\\PRICES\\'
queue = Queue.Queue()
soup_queue = Queue.Queue()
brava = threading.Lock()
links = []
brokenLinks = []
pageLinks = []
fileName = time.strftime("%d_%m_%Y-%H_%M")
class TakeURL(threading.Thread):
def __init__(self, queue, soup_queue):
threading.Thread.__init__(self)
self.queue = queue
self.soup_queue = soup_queue
def run(self):
while True:
host = self.queue.get()
try:
url = urllib2.urlopen(host)
chunk = url.read()
except:
print ("Broken link " + host)
writeCSV("BrokenLinks.csv", "ab", host)
brokenLinks.append(host)
time.sleep(30)
writeCSV('Links.csv','ab',host)
if ("class=\"price\"" in chunk):
self.soup_queue.put(chunk)
else:
writeCSV("LinksWithoutPrice.csv", "ab", host)
try:
findLinks(chunk, "ul", "mainmenu")
except:
print ("Broken Link" + host)
writeCSV("BrokenLinks.csv", "ab", host)
brokenLinks.append(host)
time.sleep(30)
self.queue.task_done()
class GetDataURL(threading.Thread):
getDataUrlLock = threading.Lock()
def __init__ (self, soup_queue):
threading.Thread.__init__(self)
self.soup_queue = soup_queue
def run(self):
while True:
chunk = self.soup_queue.get()
soup = BeautifulSoup(chunk)
dataArticle = soup.findAll("tr",{"class":""})
pagination = soup.findAll("a",{"class":"page"})
self.getDataUrlLock.acquire()
f = open(fn + fileName + ".csv", "ab")
filePrice = csv.writer(f)
for groupData in dataArticle:
for articleName in groupData.findAll("a",{"class":"noFloat"}):
fullName = articleName.string.encode('utf-8')
print (fullName)
for articlePrice in groupData.findAll("div", {"class":"price"}):
if (len(articlePrice) > 1):
fullPrice = articlePrice.contents[2].strip()
else:
fullPrice = articlePrice.get_text().strip()
print (fullPrice[:-12])
print ('-')*80
filePrice.writerow([fullName, fullPrice[:-12]])
f.close()
for page in pagination:
pageLink = page.get('href')
pageLinks.append('http://www.' + pageLink[1:])
self.getDataUrlLock.release()
self.soup_queue.task_done()
def writeCSV(fileName, writeMode, link):
try:
brava.acquire()
f = csv.writer(open(fn + fileName,writeMode))
f.writerow([link])
except IOError as e:
print (e.message)
finally:
brava.release()
def findLinks(chunk, tagName, className):
soup = BeautifulSoup(chunk)
mainmenu = soup.findAll(tagName,{"class":className})
for mm in mainmenu:
for link in mm.findAll('a'):
href = link.get('href')
links.insert(0,href)
print (href)
print ('-')*80
def startMain(links):
while (links):
#time.sleep(10)
threadLinks = links[-10:]
print ("Alive Threads: " + str(threading.activeCount()))
#time.sleep(1)
for item in range(len(threadLinks)):
links.pop()
for i in range(len(threadLinks)):
tu = TakeURL(queue, soup_queue)
tu.setDaemon(True)
tu.start()
for host in threadLinks:
queue.put(host)
for i in range(len(threadLinks)):
gdu = GetDataURL(soup_queue)
gdu.setDaemon(True)
gdu.start()
queue.join()
soup_queue.join()
if __name__ == "__main__":
start = time.time()
httpWeb = urllib2.urlopen(httpLink)
chunk = httpWeb.read()
findLinks(chunk, 'li','tab')
startMain(links)
pageLinks = list(set(pageLinks))
startMain(pageLinks)
startMain(brokenLinks)
print ('-') * 80
print ("Seconds: ") % (time.time() - start)
print ('-') * 80
答案 0 :(得分:2)
你的线程永远不会返回任何东西,所以它永远不会停止;只是不断运行while
循环。而且,由于您为每个链接启动了一个新线程,您最终只会继续添加越来越多的线程,而以前的线程可能没有做任何事情。你基本上不需要queue
的方式。正如您所注意到的,这种方法可能会导致大量工作出现问题。
worker = GetDataURL()
worker.start()
确实指向GetDataURL.run()
...这是一个无限的while循环。
TakeURL.start()
也是如此。
你可以去几条路线
1)只需暂停线程,取消队列并在run
定义的 end 处返回结果。这样每个线程有1个任务,返回结果,然后停止。不是最有效但需要最少量的代码修改。
2)在你的startMain
中,在while
循环之外,启动一组说10个线程(即线程池)。这10个线程将始终运行,而不是为每个链接启动新线程,只需将链接放入队列即可。当一个线程可用时,它将运行队列中的下一个项目。但是你仍然需要管理这些线程的清理工作。
3)您可以更多地重新编写代码,并使用内置函数,如线程池和进程池。我之前发布过流程池:SO MultiProcessing
使用这种方法,您也可以忘记与锁相关的所有混乱。在每个pool.map(或您使用的任何内容)之后,您可以将该大量信息添加到startMain
代码中的文件中。清理很多事情。
希望这有点道理。我选择不修改您的代码,因为我认为值得您尝试选项并选择方向。