对于那些不了解它的人,运行您的Python解释器并输入
import antigravity
并按Enter。 :) 对于以下代码,我很感谢任何反馈,尤其是有关线程的反馈,因为这是我的新手。
#1 楼
停止锤击XKCD服务器您基本上是在编写拒绝服务攻击,因为您尝试了尽可能快和尽可能频繁地连接到服务器。
通过共享此功能CodeReview上的代码,它变成了分布式拒绝服务。
以防万一,不清楚:不要这样做。 XKCD服务器足够高,因此您的代码不会产生任何明显的变化。不过,您的代码的主要目标应该是保持警惕。从外部看,启动脚本和随意浏览https://xkcd.com/之间应该没有什么区别。
这不仅仅出于对其他开发人员的礼貌:如果发送太多请求太快,则被远程服务器禁止。
解决方案
在下载任何图片之前至少等待一秒钟。所需的暂停时间可以在
Crawl-delay
中定义为robots.txt
。删除多线程或至少限制线程数。
如果文件已经在
SAVE_DIRECTORY
中,请不要再次下载文件。您可以检查它是否在这里,或者它的大小是否大于0
,或者它的确是PNG文件。您可能应该将其写在文件名中:geologic_time.png
可以称为2187_geologic_time.png
或02187_geologic_time.png
。评论
\ $ \ begingroup \ $
我完全不知道这一点,也无意引起或邀请其他人执行DoS和/或DDoS。我将从代码中删除线程部分。实际上,一旦我不小心写了它,我现在就完全理解DoS的内容和危险。 。
\ $ \ endgroup \ $
– Baduker
19年9月2日在8:56
\ $ \ begingroup \ $
@baduker:这不是问题,因为XKCD非常流行。将您的代码称为DDOS有点麻烦,但是我想让我的观点很清楚。在抓取信息时,目标应该是躲在雷达下。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
19年9月2日,9:19
\ $ \ begingroup \ $
感谢您的反馈,因为我对抓取领域还很陌生。但是,这是未来的宝贵经验。
\ $ \ endgroup \ $
– Baduker
19年9月2日在9:24
\ $ \ begingroup \ $
@baduker这甚至是为了使您的爬虫受益,针对爬虫/机器人的第一道防线是请求速度,如果它们在10秒内提出了5个以上的请求,我会禁止我的网站上的机器人,并且我告诉他们在我的robots.txt文件抓取延迟中:2000
\ $ \ endgroup \ $
–会计م
19年9月2日在9:26
\ $ \ begingroup \ $
@Accountantم听起来像是另一个答案。我很乐意看到用于对robots.txt进行ping操作并查询相关部分(例如遵守无机器人,抓取延迟等)的代码。
\ $ \ endgroup \ $
– Draco18s不再信任SE
19年9月2日在14:04
#2 楼
Globals就这样,将
LOGO
用作本地而不是全局将是一个优势-它仅由show_logo
使用,并将其移到那里将清理全局名称空间。 br />话虽如此(正如其他人所指出的那样),在全局范围内的Python文件顶部看到这样的内容是相当普遍的。但是,更大的问题是,如果将其移至本地范围,则必须精巧使用缩进。对此没有很好的解决方案-您必须缩进除第一行之外的所有行的缩进,这很难看;或者您必须对字符串进行后处理以删除每行开头的所有空格。因此,这是一种洗礼。尤其成问题的是:ARCHIVE = "https://www.xkcd.com/archive"
完全忽略了
BASE_URL
,而本不应该这样做。对fetch_url
有什么影响。您可以通过将参数设置为相对于基本路径的路径来使其有用。 Python具有功能齐全的requests.get
来处理URL解析和构造。在数字时间间隔上执行urllib
是不正确的。使用divmod
。评论
\ $ \ begingroup \ $
在模块的开头将硬编码值作为常量列出是怎么回事?在我看来,这是常识。
\ $ \ endgroup \ $
– Vincent
19年9月2日在8:15
\ $ \ begingroup \ $
@Vincent肯定;尽管更大的问题是缩进。我在比我最初指出的更为细微的问题上添加了一些评论。
\ $ \ endgroup \ $
– Reinderien
19年9月2日在13:08
\ $ \ begingroup \ $
@Vincent常见,并不代表最好。从我的角度来看,当我看到在模块的开头声明了一堆常量时,这些常量的名称暗示它们可能会根据情况而改变(将来的基本URL可能会更改),我认为它们属于改为配置文件。是的,在这种情况下,可能会矫kill过正,但这就是想法。
\ $ \ endgroup \ $
–ChatterOne
19年9月2日在14:03
\ $ \ begingroup \ $
@ChatterOne虽然配置文件也很好,但使用它可以导入configparser并仅出于拥有配置文件的目的处理路径。除非常量是可插入的,而不是只在文件中放置一个常量,否则这样做是没有意义的,这样,如果脚本突然停止工作,我就不需要在各处重写字符串。
\ $ \ endgroup \ $
– KeyWeeUsr
19年9月3日在8:27
#3 楼
for page in reversed(range(latest_comic - pages + 1, latest_comic + 1)):
print(f"Fetching page {page} out of {latest_comic}")
try:
url = get_images_from_page(f"{BASE_URL}{page}/")
thread = threading.Thread(target=save_image, args=(url, ))
thread.start()
except (ValueError, AttributeError, requests.exceptions.MissingSchema):
print(f"WARNING: Invalid comic image source url.")
collect_garbage.append(f"{BASE_URL}{page}")
continue
thread.join()
您在此处创建了几个下载页面的线程。此代码至少存在两个问题:您创建了多个线程,但仅join()最后创建了一个线程。不能保证所有线程都在最后一个线程之前完成。维护您的线程列表。
没有速率限制。如果您尝试下载100页,它将尝试同时完成全部100页。那不是一个好主意。一次仅创建有限数量的线程。
评论
\ $ \ begingroup \ $
就线程而言,OP最感兴趣的是什么,我想知道他们决定接受另一个答案,因为这个答案更有价值。
\ $ \ endgroup \ $
–t3chb0t
19-09-4在10:21
\ $ \ begingroup \ $
@ t3chb0t坦白地说,被接受的答案比#2更好地解释了问题#2。
\ $ \ endgroup \ $
–kutschkem
19-09-4在11:06
\ $ \ begingroup \ $
@ t3chb0t:是的,这个答案(以及此处的其他答案)包含了关于OP的宝贵信息。在这种情况下,很难选择一个可以接受的答案,而且选择一定有些武断。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
19年9月4日在14:58
#4 楼
正如Thomas的评论中提到的,另一种选择是使用XKCD的JSON接口而不是抓取HTML:import requests, time, tempfile, os.path
from shutil import copyfileobj
path = tempfile.mkdtemp()
print(path)
f_name = "{num:04d}-{safe_title}.{ext}"
current_comic = requests.get("https://xkcd.com/info.0.json").json()
# Iterates over numbers from comic 1 to the comic before current
for n in range(1,current_comic["num"]):
comic_req = requests.get("https://xkcd.com/{}/info.0.json".format(n))
# if status code is 2**
if comic_req.status_code <= 299:
comic = comic_req.json()
comic["ext"] = comic["img"][-3:]
fn = f_name.format(**comic)
img = requests.get(comic_req.json()["img"], stream=True)
with open(os.path.join(path, fn), "wb") as output:
copyfileobj(img.raw, output)
img.close()
print("Saved {}".format(os.path.join(path, fn)))
#5 楼
对于下面的代码,我非常感谢任何反馈,尤其是有关线程的反馈。
您可能希望使用ThreadPoolExecutor来管理线程。这种方法有两个优点:
执行程序可以用作上下文管理器,以确保所有线程都已连接。
它可以限制线程中的线程数。线程池。
示例:
with ThreadPoolExecutor(max_workers=8) as executor:
for page in reversed(range(latest_comic - pages + 1, latest_comic + 1)):
print(f"Fetching page {page} out of {latest_comic}")
try:
url = get_images_from_page(f"{BASE_URL}{page}/")
executor.submit(save_image, url)
except (ValueError, AttributeError, requests.exceptions.MissingSchema):
print(f"WARNING: Invalid comic image source url.")
collect_garbage.append(f"{BASE_URL}{page}")
#6 楼
在功能方面(似乎已经很好地涵盖了)不是问题,但是在清晰度/可读性方面:在功能上应为get_penultimate
(如果需要,则为get_last
),因此此名称与已实现的功能之间显然不匹配。该功能很有意义,因此名称似乎是错误的。要明确指出不匹配:倒数第二个表示倒数第二个。
#7 楼
为什么main()
是调用单个函数的一行?将main()
重命名为get_xkcd()
或直接调用get_xkcd()
。也可以使用
BeautifulSoup
代替lxml
。评论
\ $ \ begingroup \ $
如果没有main()函数,即使将脚本作为模块导入,代码也将被执行。而且,该函数还可以使用以下代码运行该代码:import module; module.main()。如果代码只是在if块中,则无法从其他地方运行。
\ $ \ endgroup \ $
– Baduker
19年9月4日在16:41
\ $ \ begingroup \ $
@baduker Tbf,我认为Kurt建议不要将任何代码移出函数。据我了解,他们的建议是将get_xkcd重命名为main或直接在if __name__ =='__main__'块中调用get_xkcd。
\ $ \ endgroup \ $
–Håkan Lindqvist
19年9月4日在19:51
\ $ \ begingroup \ $
@HåkanLindqvist正是我的建议。谢谢你的支持。
\ $ \ endgroup \ $
–库尔特
19-09-4在22:25
\ $ \ begingroup \ $
Lxml VS美丽汤,您能否在回答中详细说明?
\ $ \ endgroup \ $
– zardilior
19年9月24日在21:46
评论
我已回滚您的上一次编辑。请不要更新您问题中的代码以合并答案的反馈,否则会违反“代码审查”的“问题+答案”样式。这不是一个论坛,您应该在其中保留问题的最新版本。收到答案后,请查看您可能会做什么和可能不会做什么。尽管您希望通过删除部分代码来达到目的,但这确实使现有答案无效。我们不能拥有。
@Thomas那里有一个API?如果是这样,那将改变游戏规则!
xkcd.com/json.html
相关xkcd