python使用代理+多线程爬取速卖通评论(二)

废话少说

在上一篇文章python使用代理+多线程爬取速卖通评论(一)中,我已经成功分析出了速卖通评论请求数据的策略,但是为了防止我们的爬虫触发速卖通的反爬策略,我们决定采取使用代理IP的方式来进行伪装,同时为了提高爬取速度,我决定开多个线程进行数据爬取。
这篇文章,更多的是我在实现多线程爬取过程中的思考过程和收获,以及代码大概的说明,完整的代码我已放到github,大概300行,如有bug或者更好更优雅的实现,我会及时更新。
需要代码的看这里,代码是默认保存到数据库的,你可以本地建一下数据库和表,也可以使用我提供的save_data_to_csv()方法,直接保存到csv文件中。

使用代理IP发送请求

监控同一IP访问频率是非常常见的反爬手段之一,你用同一个IP在短时间内大量访问目标网站,而且没有sleep的话,你的ip很容易被服务器禁止访问。所以为了反反爬,我们要学会如何使用代理IP来发送请求,这也是我第一次学习使用代理IP爬数据,超easy。
对于我们个人来说,如果只是自己爬小量数据用于研究,分析的话,可以直接从代理IP网站爬取免费的代理IP。
比如国内高匿代理IP,如图

image.png

我们直接把首页的IP爬取下来就够用了,当然免费的肯定没有付费的好用,有些IP不可以用,但是说实话我还没有碰到几个不能用的。这个爬取很简单,直接附代码了,爬取到本地之后,按行保存到本地一个txt文件中

from bs4 import BeautifulSoup
import queue
url='http://www.xicidaili.com/nn/'
headers={
      'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',        
}
ip_data=requests.get(url,headers=headers)
soup=BeautifulSoup(ip_data.text,'html.parser')
ips=soup.select('tr')
ip_list=[]
for i in range(1,len(ips)):
    ip_info=ips[i]
    tds=ip_info.select('td')
    ip_list.append(tds[1].text+':'+tds[2].text)
with open("iplist.txt","a",encoding='utf-8') as f:
        for ip in ip_list:
            f.write(ip)
            f.write('\n')
image.png

接着你就可以用代理IP来爬数据了,听起来感觉很复杂,但是对应到代码上,也就多加一个参数(当然只限定python+requests,其它不了解)

def get_ip_list(self):
    temp_ip_list=[]
    with open('iplist.txt', 'r') as f:
        while True:
            ip=f.readline().replace('\n','')
            # 记得加 'http://'
            temp_ip_list.append('http://'+ip)
            if not ip:
                break
    return temp_ip_list       
def get_random_ip(self):        
    proxy_ip=random.choice(self.ip_list)
    proxies={
        'http':proxy_ip
    }
    return proxies

因为我是从完整代码里截取的,所以里边有self,第一个函数用于将每一个ip从iplist.txt这个文件读取到一个python list中,然后第二个函数用于从该list中随机获取一个Ip,而真正使用代理ip发请求是超级简单,只是在原来的基础上,多一个proxies参数

proxies=self.get_random_ip()
requests.get(url,headers=headers,proxies=proxies)

多线程爬取

我回家连的就是家里wifi,本来爬的就慢,动不动就超时了,再加上,为了保险,每爬一个页面,我都sleep1秒钟,这样一来,爬取的速度我感觉有点慢,所以就考虑要不要多开几个线程,但是因为对之前从来没写过多线程程序,对多线程的认识就是一些模糊的概念,因此在编程中间还碰到一些问题,但是后来解决问题之后,对并发编程,线程同步,生产者与消费者模型,线程安全等有一个更进一步的认识。
代码整体结构如图


image.png

CommentSpyder 是爬虫类,主要负责爬取数据和解析数据
Saver是存储类,主要负责储存数据
get_total_page()函数用于获取评论总页数
get_url()函数用于构造token请求地址
update_ip_list()函数用于更新iplist.txt文件中的代理IP,需要手动执行
crawl()为封装好的爬取函数
main()函数为主函数


image.png

在main函数中,首先发出请求获取总页数,然后根据总页数给每个线程平均分配自己所要爬取的评论页码范围,默认开10个线程,同时在开一个线程,用于往数据库或者csv文件写数据,然后这10个线程相当于生产者-消费者模型中的生产者,saver线程相当于是消费者,这11个线程共享一个pyhon提供的线程安全的队列,生产者爬到数据之后写入该队列,然后消费者从该队列取数据,并一条一条插入数据库或者保存到csv中。
说一下我踩过的坑
踩坑1:我在爬虫类初始化的时候首先发一个请求,获取token,这样在爬取每一页的时候就不必每次去取token了,但是我在写多线程的时候,一开始是这么写的,只贴相关代码
spyder=CommentSpyder(url,productid,owner_memberid,companyid,result_queue,start_page,end_page)
crawl_thread = threading.Thread(target = spyder.crawlComments,args=(url,productid,owner_memberid,companyid,result_queue,start_page,end_page))

一开始一直没觉得有什么问题,但是当我发现多线程跑和单线程跑的时间差不多的时候,我突然想起了,学pyhon基础的时候有一个GIL(python全局解释器锁),然后又从别人博客中看到所谓的“python多线程是鸡肋的言论”,于是我恍然大悟,“怪不得多线程时间和单线程时间差不多嘞,原来python多线程没什么鸟用”。
但是当我百度输入python多线程爬虫,还是有很多人用python的多线程来写代码,如果真的没用,为什么还有这么多人采取多线程,所以我还是多思考了一会,终于想清楚了原因。
因为我为每个线程实例化了一个爬虫对象,而在爬虫对象初始化的过程中,会发出网络请求取得token,而我给thread添加的target中只有python爬取数据的代码,所以这十个爬虫对象请求token的过程是线程阻塞的,这也是为什么我总感觉线程是一个个按顺序运行的,我一开始还误以为是全局解释器锁的原因,每次只能有一个线程获得锁,很显然是我错了,python的多线程鸡肋只是鸡肋在无法利用多核CPU,但是即使单核CPU,在做IO密集型操作时,多线程效率还是远远高于单线程
我也曾一度钻进牛角尖,我想不通,单核CPU多线程的时间为什么会比单线程短...
因为,学习多线程的时候,经常讲到一个时间片的切换,微观上是一个个操作来的,只是切换足够快,快到看上去就好像计算机在同时做两个操作。那么既然实际上是按顺序一个个运行的,只是看上去在并行,那么多线程时间怎么会缩短呢?假设有两个任务A和B
A中包含a1,a2俩个操作,分别耗时1s,2s
B中包含b1,b2,b3三个操作,分别耗时1s,2s,3s
同步运行的话肯定是9s(当然简化了模型)
就算开了多线程,单核CPU,不管你切换的有多快,但是本质上你一次只做一个操作,你完成了a1,切换到b1,不管怎么切换,最终运行时间也应该等于9秒才对啊。
而通过写这个多线程爬虫,也让我想通了这个问题,我之所以有上述错误的想法就是因为我忽略了IO往往存在大量的阻塞时间。
任务AB耗费的总时间等于AB操作+IO阻塞的时间(如网络IO,磁盘IO),而相比IO阻塞时间,cpu执行操作的时间几乎可以忽略不计。
那么再以上面那个例子来讲一下
同步执行的情况下
a1 1秒,等待IO10秒
a2 2秒,等待IO20秒
b1 1秒,等待IO10秒
b2 2秒,等待IO20秒
b3 3秒,等待IO30秒
总耗时99秒
而使用多线程的话,
a1 1秒 ,遇到IO阻塞,释放GIL锁,而不会傻等在这里,线程B获得GIL,转去执行b1,说到这里,后面就不用说了吧,这样下来总时间肯定少于99秒。所以时间可以缩短全是因为IO阻塞的存在。
踩坑2:一开始我只开了10个线程,在爬到数据并解析后立刻插入数据库,但是数据库这边有时候会报链接不可以获得的错误,我猜测肯定是数据库访问频率某个瞬间太高了,后来就想要不用个队列,爬下来先写到队列里,然后再开一个线程,专门用于从队列中慢慢读,并保存到数据库,写着写着,哇,这不就是操作系统上讲的消费者与生产者模型嘛。
踩坑3:一开始我在保存数据的时候,想要打印一个信息,即这是第几条数据,但是经常会出现多个线程打印同一个数字,这是因为我没有进行加锁,当我加锁之后,对该变量的读取和加1操作每一个时刻只有一个线程可以运行,从而打印出了正确的顺序,这似乎没什么,稍微了解一下锁的概念就可以知道,但是后边我在用一个共享队列的时候,我并没有加锁,但是我发现从来没有出现多个线程同时访问一条数据的情况,我试了很多遍,一次都没有出现,我突然,(真的是突然),想起了一个词“线程安全”,前段时间看java,总是说哪些容器是线程安全的,哪些是不安全的,肯定就是这儿的这个意思,我百度一查,果然如此,import queue进来后,我使用的是python自带的线程安全队列,该队列内部实现了锁原语,所以保证了不会有多个线程对其同时进行读写,如果你换成list,肯定就有问题了。

最后

踩坑越多,收获越大,我知道我的智商只是正常人的智商,无论我怎么思考也解决不了世界难题,但是思考总是可以让我进步,让我更优秀,所以希望我永远热爱思考,永远享受想通问题时的畅快!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容