程序员不讲武德,为女朋友乱用多线程!

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次,出验证码,验证通过了才能继续访问。

如果本文阅读上千,大家又有需求,我们可以再说说这几个。

最后,我劝年轻人耗子尾汁,不管是开发网站,开始开发爬虫,都要讲武德,不要大意!

点赞,转发,点在看,就是给我最大的帮助,谢谢!

(0)

相关推荐