程序员不讲武德,为女朋友乱用多线程!
1. 为爱出码
就在昨天,有个朋友忽然给我发了一条私信:
麦叔,想看笑话吗?
我说:看你的笑话啊!有什么好笑的?
他说:不是,我发现一个笑话网站,上面有很多笑话!
我说:然后呢?
他说:那个网站程序员好像比较笨,我想写个程序把笑话都抓下来,每天给女朋友发送1个笑话!
我说:哦,你去抓吧。我没有女朋友,不需要。
2. 笨程序员
在他的死皮赖脸的央求下,我去看了一下那个笑话网站的页面:
http://xiaohua.zol.com.cn/detail1/1.html
这个网站果然是有漏洞的:
- 首先网站没有使用https,这个现代网站的基本标配都没有。
- 然后它的URL很容易被猜测,你看1.html,那是不是有2.html呢?试了一下还真有。
这就简单了,要抓它就顺藤摸瓜,1,2,3...100000抓下去就是了。
可以说这个网站的爬虫防守几乎没做。
3. 简单爬虫
几分钟后,他乐呵呵的哭丧着脸又来找我。
对,没错!他乐呵呵的是因为很快就写好了爬虫,也抓取了一些笑话。哭伤着脸是因为抓了没几下程序就挂了。
来看看他的程序:
import requests
import bs4
url = 'http://xiaohua.zol.com.cn/detail1'
with open('joke.txt') as f:
for joke_id in range(1, 100000):
response = requests.get(f'{url}{joke_id}.html')
soup = bs4.BeautifulSoup(response.text, 'lxml')
joke_text = soup.select('div.article-text')[0].getText().strip()
f.write(f'{joke_id}, {joke_text}\n')
他代码写的还算简洁:
使用requests.get抓取网页内容,动态拼接网页的URL,都要感谢网站程序员的没防御啊。 使用BeautifulSoup把笑话的文本解析出来。 保存到joke.txt中。 好家伙,这一口气要抓10万个笑话,你有几个女朋友啊??
表面上程序还行,但我用我的不太近视的近视眼瞄了一眼,就知道这个程序一定活不了多久,你想想看问题在哪里。
4. 得优化
上面的程序在电视剧里顶多活一集,因为如果任何一个网络请求报错了,这个程序就挂啦!网络请求报错是很正常的事情,很多原因都可能会造成网络请求失败!
这得改,必须得改!
import requestsimport bs4
url = 'http://xiaohua.zol.com.cn/detail1'with open('joke.txt') as f: for joke_id in range(1, 100000): try: response = requests.get(f'{url}{joke_id}.html') soup = bs4.BeautifulSoup(response.text, 'lxml') joke_text = soup.select('div.article-text')[0].getText().strip() f.write(f'{joke_id}, {joke_text}\n') except Exception as e: print('笑话没抓到,继续抓下一个')
通过把网络请求放到try except中,如果请求出错了,只会打印一句“笑话没抓到,继续抓下一个',至少程序不会停掉!
这货肯定活的的稳稳的!
但是,你活得太久了也不行啊。这10万条数据,你得抓多久啊!女朋友要说:你不行!
这得改,必须得改!
5. 多线程
这还不好改,用多线程:
import requests
import bs4
import threading
url = 'http://xiaohua.zol.com.cn/detail1'
def get_joke(joke_id, file):
response = requests.get(f'{url}{joke_id}.html')
soup = bs4.BeautifulSoup(response.text, 'lxml')
joke_text = soup.select('div.article-text')[0].getText().strip()
file.write(f'{joke_id}, {joke_text}\n')
with open('joke.txt') as f:
for joke_id in range(1, 100000):
try:
threading.Thread(target=get_joke, args=(joke_id,))
except Exception as e:
print('笑话没抓到,继续抓下一个')
代码说明:
引入了threading模块 把抓取笑话的代码放到一个函数中 给每个笑话创建独立的线程去抓取
运行一下看看,应该没问题。可是,他的电脑爆啦!!!
因为短时间内启动了太多的线程。
这个得控制一下,这个必须控制。
6. 线程池
简单,使用线程池,控制线程的个数:
import requestsimport bs4import concurrent.futures
url = 'http://xiaohua.zol.com.cn/detail1'def get_joke(joke_id, file): response = requests.get(f'{url}{joke_id}.html') soup = bs4.BeautifulSoup(response.text, 'lxml') joke_text = soup.select('div.article-text')[0].getText().strip() file.write(f'{joke_id}, {joke_text}\n')
with open('joke.txt') as f: with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor: for joke_id in range(1, 100000): try: executor.submit(get_joke, joke_id) except Exception as e: print('笑话没抓到,继续抓下一个')
代码说明:
引入了concurrent模块 创建了一个线程池,最多100个线程 把每个抓取任务提交给线程池,最多100个线程干活,多余得排队
...几小时后...终于大功告成!
感觉,学会编程还真挺好的!
7. 结果
几天以后,他又来找我,像是霜打的茄子,原来女朋友和他分手了!
他说:没想到,到头来,我就是个笑话。
我说:没想到,真看了你的笑话。是怎么回事?
原来,他没有人工审核笑话,结果程序随机给女朋友发了3个笑话就结束了他们的友谊:
2020-11-17:
问:女朋友丑怎么办?
答:丑不是她的错,都是爹妈给的,你想让她漂亮就去韩国整容,如果你没钱,你还嫌她丑就是你的错,要么接受要么换。
2020-11-18:
如果我们分手,白天倒还好,可一到晚上就再也抑制不了内心的感情,一个人蒙在被子里偷偷地笑了起来。
2020-11-19日:
今天和女朋友分手了,总是有着那么的一些事情让人心疼。
经过内心的挣扎,我终于鼓起勇气拨通了电话:“喂,是移动吗?嗯,是这样的,我和我女朋友分手了,我前天帮她冲了两百话费,你能帮我把它要回来吗?”
8. 后记:
爬虫程序还有改进的空间:
这么大量的线程来自同一个IP地址,8成会被封掉的,可以考虑使用动态换代理,防止IP被封 可以使用协程进一步提高效率。
如果你是那个开发网站的程序员,你要怎么防御:
网址不要用很容易猜测的数字作为笑话的编号,使用随机生成的长字符串,你看看淘宝的网址你就知道了。 防止某一个IP短时间内过多访问你的网站。 连续访问10次,出验证码,验证通过了才能继续访问。
如果本文阅读上千,大家又有需求,我们可以再说说这几个。
最后,我劝年轻人耗子尾汁,不管是开发网站,开始开发爬虫,都要讲武德,不要大意!
点赞,转发,点在看,就是给我最大的帮助,谢谢!