说起python爬虫,有一个非常知名的框架Scrapy。异步爬取,简单易用,功能强大。拉条先生,学着练着吧。BakerStreet,一个推理爱好者的论坛网站,用户数据量在12W左右,非常适合Scrapy学习和练习爬虫。本文的前半部分将介绍一些基础知识。毕竟要照顾小白同学啊~提示:本文仅供学习交流之用,请勿用于非法用途!!!01.写在前面的话,本篇博客会在写代码的同时简单介绍一下Scrapy的框架。与拉条老师写的前两篇爬虫博客相比,本篇博客爬取的数据量更大。在写代码之前,我想先说说贝克街的网站。两三年前,我去过这个网站几天。那时候,好像只有五万到六万人。现在要发展到超过12万用户并不容易。一群推理爱好者的精神家园。这个网站好像没有任何反爬措施。拉条先生在再次确认免责声明的同时,呼吁大家多多学习交流,不要把别人的服务器弄坏了。同时本博客爬取的链接都在BakerStreetrobots文件要求之外,绝对ok~02。Scrapy安装首先需要安装lxml库pipinstalllxml然后到下面两个链接安装与你本地python版本一致的whl文件pywin32twisted然后安装上面两个库pipinstall你完整的pywin32whl文件路径pipinstall你完整的twistedwhl文件路径例如:pipinstallC:\Users\Administrator\Downloads\pywin32-228-cp38-cp38-win_amd64.whl最后就可以安装Scrapy了,pipinstallscrapy看看是否安装成功,我们安装完成。03.项目结构介绍Scrapy为我们提供了一些有用的命令行,比如上一节的scrapy-h。我们也可以使用命令行创建一个项目scrapystartprojectbeikejie然后,我们得到如下的项目结构。简单介绍下几个文件BeiKeJieSpider.py:一个爬虫,我们的代码主要写在里面,后面会详细介绍。items.py:数据实例,一种数据结构。pipelines.py:数据爬取后,明确存放数据的地方。middlewares.py:一些中间件,这里可以在每次请求前设置proxy,cookie等。本项目没有使用该模块,毕竟没有防爬措施。settings.py:一些项目设置,可以设置很多东西,包括pipelines在pipelines中的优先级等等,后面会详细介绍。scrapy.cfg:一些全局设置,基本不适用于本项目。04.需求分析本次爬取的目的是获取贝克街的所有用户信息思路:网站上的一群大V,爬取他们的followlist和fanlist,然后从某个follower或fan入手,继续爬取其关注列表和粉丝列表。这样可以爬到大部分用户,但并不是所有的用户都能爬到,因为毕竟可能存在没有关注者、没有粉丝的用户孤岛。那么我们要做的就是:根据某个用户的主页获取一些用户信息。获取一个用户的followlist,获取每个follower的主页,执行第一步。获取一个用户的粉丝列表,获取每个粉丝专页,执行第一步。05.获取用户信息首先,我们需要写一个mongo函数,根据用户的主页获取用户信息并存储。首先在BeiKeJieSpider中编写代码,这里是编写爬虫主要逻辑的地方。classBeiKeJieSpider(scrapy.Spider):name="beikejie"logger=logging.getLogger()allowed_domains=['tuilixy.net']cookies={'你自己的cookie'}defstart_requests(self):urls=['http://www.tuilixy.net/space-uid-45001.html']forurlinurls:yieldscrapy.Request(url=url,callback=self.parse)defparse(self,response):item=self.main_page_parse(response)yielditem#解析主页数据defmain_page_parse(self,response):select=Selector(response)uid=select.xpath('//*[@id="main"]/div[2]/div/div/div[2]/div[1]/h1/span/text()').get(default='--').split('')[1]name=select.xpath('//*[@id="main"]/div[2]/div/div/div[2]/div[1]/h1/text()').get(default='-')register_time=select.xpath('//*[@id="pbbs"]/tbody/tr[2]/td[2]/text()').get(default='-')follower_numbers=select.xpath('//*[@id="ct"]/div[2]/div[1]/ul/a[1]/li/h4/text()').get(默认=0)fans_numbers=select.xpath('//*[@id="ct"]/div[2]/div[1]/ul/a[2]/li/h4/text()').get(default=0)item=BeikejieItem()item['uid']=uiditem['name']=nameitem['register_time']=register_timeitem['follower_numbers']=follower_numbersitem['fans_numbers']=fans_numbersreturn上面item我们说了,这个类其实就是一个爬虫,name是爬虫的名字,allowed_domains是这个爬虫可以爬取的域名,start_requests是爬取的起始页,urls是那些大V的主页,为了方便说明,我们这里已经从某大V的首页爬取完毕,进入回调函数parse进行分析。注意该方法返回的数据使用yield。这个关键字实际上生成了一个迭代器。再次进入函数时,会从yield继续执行,后面会有神奇的效果。解析后会返回一个BeikejieItem的实例。由于该项目已返回,pipelines.py将用于进一步处理。那么我们先来看数据结构BeikejieItem所在的items.py文件。classBeikejieItem(scrapy.Item):uid=scrapy.Field()name=scrapy.Field()register_time=scrapy.Field()follower_numbers=scrapy.Field(serializer=int)fans_numbers=scrapy.Field(serializer=int)required继承scrapy.Item,然后定义一些需要存储的数据字段。可以看到,该字段还可以设置存储类型。数据结构有了,我们再来看管道处理pipelines.py文件。类MongoPipeline:def__init__(self,mongo_uri,mongo_db):self.mongo_uri=mongo_uriself.mongo_db=mongo_dbself.mongo_collection=None@classmethoddeffrom_crawler(cls,crawler):returncls(mongo_uri=crawler.settings.get('MONGO_URI'),mongo_db=crawler.settings.get('MONGO_DATABASE','items'))defopen_spider(self,蜘蛛):self.client=MongoClient(self.mongo_uri)self.db=self.client[self.mongo_db]self.mongo_collection=self.db['beikejie']defclose_spider(self,spider):self.client.close()defprocess_item(self,item,spider):self.mongo_collection.insert_one(ItemAdapter(item).asdict())返回项目类DuplicatesPipeline:def__init__(self):self.ids_seen=set()defprocess_item(self,item,spider):adapter=ItemAdapter(item)ifadapter['uid']inself.ids_seen:raiseDropItem(f"Duplicateitemfound:{item!r}")else:self.ids_seen.add(adapter['uid'])returnitemdefclose_spider(self,spider):print(self.ids_seen)分别有两条Pipelines的传入的项目将被处理,优先级将在文件settings.py中配置。先看看这两条Pipeline。DuplicatesPipeline是为了重用。内存中维护了一个集合,用于存放库中存储的uid,避免Repeat,存在则报错。其实这篇博客的去重并不完美。首先是内存问题,12万个元素的大集合维护是个问题,而且没有持久化(当然一开始可以从mongo读取所有库中存在的uid),不用考虑分配。未来后期去重应该交给Redis这个缓存中间件,这里只是演示。第二个MongoPipeline用于mongo存储。仔细看看~先执行类方法from_crawler从配置文件settings.py中读取mongo库的信息,然后执行__init__初始化信息,初始化实例属性mongo_uri和mongo_db。然后执行open_spider,启动爬虫时会执行该方法,生成一个真正的数据库链接mongo_collection。每次进入管道都会执行process_item方法进行数据插入。close_spider方法,顾名思义,只有在爬虫关闭时才会执行,数据库连接也会关闭。已经引入了用于重复数据删除和存储的管道。我们查看一下配置信息,看settings.py。ITEM_PIPELINES={'beikejie.pipelines.DuplicatesPipeline':299,'beikejie.pipelines.MongoPipeline':300}MONGO_URI='127.0.0.1:27017'MONGO_DATABASE='pjjlt'ITEM_PIPELINES是开启以上两条管道,数字越小,优先级越高,重复数据删除优先于存储库管道是合乎逻辑的。下面是数据库配置信息。当然settings.py也有很多配置信息,非常有用,大家可以自行阅读~以上,我们实现了一个用户首页数据的读取和存储。下面我们进入第二步和第三步。06.获取关注者列表回到贝壳街蜘蛛类,我们继续爬取关注者列表。可以看出关注者列表是分页的,我们可以根据下一页按钮获取下一页url进行翻页操作。关注者列表的每个页面都可以获取到自己的uid,这样我们就可以拼凑出每个关注者的首页url。提交代码~cookies={'yourowncookie'}defparse(self,response):item=self.main_page_parse(response)yielditemuid=item['uid']try:##点击以下链接进入关注页面,爬取每个关注者的信息http://www.tuilixy.net/home.php?mod=follow&uid=45001&do=followingfollower_url=f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=following'yieldscrapy.Request(url=follower_url,cookies=self.cookies,callback=self.follower_parse)除了异常为e:logging.error("Failed:uid:"+uid+"\n错误原因是:"+str(e))deffollower_parse(self,response):logging.info('开始爬取followlist,'+response.url)doc=pq(response.text)lis=doc('.flw_ulist.prw.plw').children('.ptf.pbf.cl')forliinlis:try:url_end=pq(li)('.z.avt.w60.br4').attr('href')ifurl_end:url='http://www.tuilixy.net/'+url_endyielddscrapy.Request(url=url,callback=self.parse)exceptExceptionase:logging.error("爬取注意力失败:url:"+response.url+"\n错误原因为:"+str(e))try:#翻页turn_page_url_start=doc('.nxt').attr('href')ifturn_page_url_start:turn_page_url='http://www.tuilixy.net/'+turn_page_url_startyieldscrapy.Request(url=turn_page_url,cookies=self.cookies,callback=self.follower_parse)exceptExceptionase:logging.error("Failedtocrawlfollower:url:"+response.url+"\n错误原因是:"+str(e))首先,补充一下我们上面写的parse方法,存储一个用户的信息后,爬取用户follower列表的第一页,获取followed列表的第一页,执行回调函数follower_parse。follower_parse主要做了两件事,获取本页面所有用户的uid,拼凑出他们的用户主页url,执行回调函数parse获取用户主页。(这是我们做需求分析的第一步)。第二件事是获取下一页的url,翻页,获取下一页的关注者列表,执行回调函数follower_parse获取关注者列表。依此类推,直到将用户的关注者列表翻到最后一步。请求follower列表,需要添加cookie,cookie需要从浏览器获取(需要登录,而且已经爬过了,不能注册账号是吧?)。值得一提的是,在Scrapy的Request方法中,设置cookie必须显示setting,而不是把cookie放在headers中!这个知识点消耗了辣条君找问题的时间,还是太好了。.知道打开Request源码。以上,我们就完成了一个用户关注列表的爬取。07.获取粉丝列表和爬粉丝是一样的逻辑,直接上传代码即可。defparse(self,response):item=self.main_page_parse(response)yielditemuid=item['uid']try:##点击follow链接进入follow页面,爬取每个follower的信息http://www.tuilixy.net/home.php?mod=follow&uid=45001&do=followingfollower_url=f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=following'yieldscrapy。request(url=follower_url,cookies=self.cookies,callback=self.follower_parse)##点击follower链接,进入粉丝页,爬取每个follower的信息http://www.tuilixy.net/home。php?mod=follow&uid=45001&do=followerfans_url=f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=follower'yieldscrapy.Request(url=fans_url,cookies=self.cookies,callback=self.fans_parse)exceptExceptionase:logging.error("Failure:uid:"+uid+"\n错误原因是:"+str(e))deffans_parse(self,response):logging.info('开始抓取粉丝列表,'+response.url)doc=pq(response.text)lis=doc('.flw_ulist.prw.plw').children('.ptf.pbf.cl')forliinlis:try:url_end=pq(li)('.z.avt.w60.br4').attr('href')ifurl_end:url='http://www.tuilixy.net/'+url_endyieldscrapy.Request(url=url,callback=self.parse)除了Exceptionase:logging.error("抓取粉丝失败:url:"+response.url+"\n错误原因是:"+str(e))#翻页try:turn_page_url_start=doc('.nxt').attr('href')如果turn_page_url_start:turn_page_url='http://www.tuilixy.net/'+turn_page_url_startyieldscrapy.Request(url=turn_page_url,cookies=self.cookies,callback=self.fans_parse)除了Exceptionase:logging.error("爬扇失败:url:"+response.url+"\n错误原因为:"+str(e))08.写好运行逻辑后,添加try、except、key注释,使用Scrapy命令行进行操作,Let'sstartrunningscrapycrawlbeikejiecrawl是执行一个爬虫,后面的参数是爬虫的名字。由于辣条君的原始大Vurl只有一个,所以只爬取了部分数据,只有16429条用户信息,耗时约2小时。可以多选几个大V,选的越多数据越接近12W。后期我们也可以利用分布式的优势,多开几个scrapy实例来提高爬取速度。09.结束与前面两篇博客相比,这篇Scrapy数据量大,写代码时间长。希望各位读者会喜欢~喜欢的话请点个赞再走。您的支持很热Tiao先生感激不尽。最后,如果有朋友需要完整代码,请在评论区留下邮箱,或者通过CSDN私信辣条君获取。吃代码的时候,请轻一点,服务器会受伤的。逃跑~
