話說python爬蟲界,有個(gè)非常知名的框架Scrapy雹嗦。異步爬取范舀,使用簡(jiǎn)單合是,功能強(qiáng)大。小編學(xué)習(xí)之锭环,練習(xí)之聪全。貝克街,一個(gè)推理愛好者論壇網(wǎng)站辅辩,用戶數(shù)據(jù)量12W左右荔烧,很適合Scrapy學(xué)習(xí)練習(xí)爬取。本篇前半部分會(huì)介紹一點(diǎn)點(diǎn)基礎(chǔ)汽久,畢竟要照顧小白同學(xué)們嘛~
Tip:本文僅供學(xué)習(xí)與交流鹤竭,切勿用于非法用途!>按肌臀稚!
01. 寫在前面的話
本博客在編寫代碼的同時(shí),會(huì)簡(jiǎn)單介紹Scrapy這個(gè)框架三痰。相較于小編寫的前兩篇爬蟲博客吧寺,本篇博客爬取的數(shù)據(jù)量較大。
在寫代碼之前散劫,我想說下貝克街這個(gè)網(wǎng)站稚机。在兩三年前,我上過幾天這個(gè)網(wǎng)站获搏,當(dāng)時(shí)好像也就5赖条、6萬人,現(xiàn)在發(fā)展到12W多用戶挺不容易的常熙。一群推理愛好者的精神家園纬乍。本網(wǎng)站好像也沒什么反爬措施,在再次申明免責(zé)聲明的同時(shí)裸卫,小編懇請(qǐng)大家茬腿,僅僅學(xué)習(xí)交流奕删,不要把人家服務(wù)器搞崩了哦卑笨。同時(shí)甜熔,本博客爬取的鏈接都在貝克街robots文件要求以外,絕對(duì)ok~
?
如果大家在學(xué)習(xí)中遇到困難聋袋,想找一個(gè)python學(xué)習(xí)交流環(huán)境队伟,可以加入我們的python圈,裙號(hào)930900780舱馅,可領(lǐng)取python學(xué)習(xí)資料缰泡,會(huì)節(jié)約很多時(shí)間,減少很多遇到的難題代嗤。
02. Scrapy安裝
首先需要安裝 lxml庫
pip install lxml
復(fù)制代碼
然后分別去以下兩個(gè)鏈接棘钞,安裝和自己本機(jī)python版本一致的 whl 文件
pywin32 twisted
接著安裝上面那兩個(gè)庫
pip install 你完整的pywin32whl 文件路徑
pip install 你完整的twistedwhl 文件路徑
例如:
pip install C:\Users\Administrator\Downloads\pywin32-228-cp38-cp38-win_amd64.whl
復(fù)制代碼
最后,可以安裝Scrapy了
pip install scrapy
復(fù)制代碼
查看一下是否安裝成功
?
以上干毅,我們就安裝完畢了宜猜。
03. 項(xiàng)目結(jié)構(gòu)簡(jiǎn)介
Scrapy為我們提供了一些好用的命令行,比如上一節(jié)的scrapy -h硝逢。我們還可以使用命令行創(chuàng)建項(xiàng)目
scrapy startproject beikejie
復(fù)制代碼
然后姨拥,我們得到了如下的項(xiàng)目結(jié)構(gòu)
?
簡(jiǎn)單介紹下幾個(gè)文件
BeiKeJieSpider.py:一個(gè)爬蟲,咱們代碼主要寫和里面渠鸽,后面會(huì)詳細(xì)說叫乌。 items.py:數(shù)據(jù)實(shí)例,一個(gè)數(shù)據(jù)結(jié)構(gòu)徽缚。 pipelines.py:數(shù)據(jù)爬取之后憨奸,進(jìn)行數(shù)據(jù)清晰儲(chǔ)存的地方。 middlewares.py:一些中間件凿试,這里可以設(shè)置每次請(qǐng)求前的代理排宰、cookie等。本項(xiàng)目不使用這個(gè)模塊那婉,畢竟人家沒什么反爬措施嘛板甘。 settings.py:一些項(xiàng)目的設(shè)置,可以設(shè)置很多東西详炬,包括 pipelines 內(nèi)管道的優(yōu)先級(jí)等等盐类,后面用到的地方會(huì)詳細(xì)說。 scrapy.cfg:一些全局設(shè)置呛谜,本項(xiàng)目基本不適用傲醉。
04. 需求分析
本次爬取的目的,是獲取貝克街所有的用戶信息
思路:一批網(wǎng)站的大V呻率,爬取他們的關(guān)注列表和粉絲列表硬毕,然后再以某個(gè)關(guān)注者或者粉絲為起點(diǎn),繼續(xù)爬取其關(guān)注列表和粉絲列表礼仗。這樣可以爬取大部分用戶吐咳,并不能爬取全部,因?yàn)楫吘箍赡軙?huì)有無關(guān)注無粉絲的用戶的用戶孤島元践。
所以我們要做的是:
根據(jù)某用戶主頁韭脊,獲取一些用戶信息。
獲取某用戶的關(guān)注列表单旁,獲取每個(gè)關(guān)注者主頁沪羔,并執(zhí)行第一步。
獲取某用戶的粉絲列表,獲取每個(gè)粉絲主頁蔫饰,并執(zhí)行第一步琅豆。
05. 獲取用戶信息
首先,我們需要編寫根據(jù)某用戶主頁篓吁,獲取用戶信息茫因,并儲(chǔ)存mongo功能。
?
先在 BeiKeJieSpider中編寫代碼杖剪,這個(gè)是爬蟲主要邏輯編寫的地方冻押。
class BeiKeJieSpider(scrapy.Spider):
? ? name = "beikejie"
? ? logger = logging.getLogger()
? ? allowed_domains = ['tuilixy.net']
cookies = {'你自己的cookie'}
? ? def start_requests(self):
? ? ? ? urls = [
? ? ? ? ? ? 'http://www.tuilixy.net/space-uid-45001.html'
? ? ? ? ]
? ? ? ? for url in urls:
? ? ? ? ? ? yield scrapy.Request(url=url, callback=self.parse)
? ? def parse(self, response):
? ? ? ? item = self.main_page_parse(response)
? ? ? ? yield item
? ? # 解析主頁數(shù)據(jù)
? ? def main_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(default=0)
? ? ? ? fans_numbers = select.xpath('//*[@id="ct"]/div[2]/div[1]/ul/a[2]/li/h4/text()').get(default=0)
? ? ? ? item = BeikejieItem()
? ? ? ? item['uid'] = uid
? ? ? ? item['name'] = name
? ? ? ? item['register_time'] = register_time
? ? ? ? item['follower_numbers'] = follower_numbers
? ? ? ? item['fans_numbers'] = fans_numbers
? ? ? ? return item
復(fù)制代碼
上面我們說了,這個(gè)類實(shí)際上就是一直爬蟲盛嘿,name就是爬蟲的名字洛巢,allowed_domains是此爬蟲可以爬取的域名,start_requests是起始爬取頁面次兆,這里面urls就是那些大V的主頁稿茉,為了方便說明,我們這邊從一個(gè)大V的主頁開始类垦。
爬取完了狈邑,進(jìn)入回調(diào)函數(shù)parse進(jìn)行解析,注意此方法內(nèi)返回?cái)?shù)據(jù)使用的是yield蚤认,此關(guān)鍵字實(shí)際上是生成了一個(gè)迭代器米苹,再次進(jìn)入函數(shù)時(shí),會(huì)接著從yield處開始執(zhí)行砰琢,后面會(huì)有妙用蘸嘶。然后解析完了,會(huì)返回一個(gè)BeikejieItem實(shí)例陪汽,因?yàn)榉祷氐氖莍tem训唱,所以會(huì)讓pipelines.py進(jìn)行進(jìn)一步處理。
那么們先看下數(shù)據(jù)結(jié)構(gòu)BeikejieItem所在的items.py文件吧挚冤。
class BeikejieItem(scrapy.Item):
? ? uid = scrapy.Field()
? ? name = scrapy.Field()
? ? register_time = scrapy.Field()
? ? follower_numbers = scrapy.Field(serializer=int)
? ? fans_numbers = scrapy.Field(serializer=int)
復(fù)制代碼
需要繼承scrapy.Item况增,然后定義一些需要儲(chǔ)存的數(shù)據(jù)字段⊙档玻可以看到澳骤,字段還可以設(shè)置儲(chǔ)存類型。
有了數(shù)據(jù)結(jié)構(gòu)澜薄,那么接著看下管道處理pipelines.py文件吧为肮。
class MongoPipeline:
? ? def __init__(self, mongo_uri, mongo_db):
? ? ? ? self.mongo_uri = mongo_uri
? ? ? ? self.mongo_db = mongo_db
? ? ? ? self.mongo_collection = None
? ? @classmethod
? ? def from_crawler(cls, crawler):
? ? ? ? return cls(
? ? ? ? ? ? mongo_uri=crawler.settings.get('MONGO_URI'),
? ? ? ? ? ? mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
? ? ? ? )
? ? def open_spider(self, spider):
? ? ? ? self.client = MongoClient(self.mongo_uri)
? ? ? ? self.db = self.client[self.mongo_db]
? ? ? ? self.mongo_collection = self.db['beikejie']
? ? def close_spider(self, spider):
? ? ? ? self.client.close()
? ? def process_item(self, item, spider):
? ? ? ? self.mongo_collection.insert_one(ItemAdapter(item).asdict())
? ? ? ? return item
class DuplicatesPipeline:
? ? def __init__(self):
? ? ? ? self.ids_seen = set()
? ? def process_item(self, item, spider):
? ? ? ? adapter = ItemAdapter(item)
? ? ? ? if adapter['uid'] in self.ids_seen:
? ? ? ? ? ? raise DropItem(f"Duplicate item found: {item!r}")
? ? ? ? else:
? ? ? ? ? ? self.ids_seen.add(adapter['uid'])
? ? ? ? ? ? return item
? ? def close_spider(self, spider):
? ? ? ? print(self.ids_seen)
復(fù)制代碼
這里有兩個(gè)Pipeline分別都會(huì)處理傳進(jìn)來的item,優(yōu)先級(jí)待會(huì)會(huì)被配置到文件settings.py里面肤京。
先看下這兩個(gè)Pipeline颊艳,DuplicatesPipeline是去重用的,內(nèi)存中維護(hù)了一個(gè)set,存放存入庫中的uid棋枕,避免重復(fù)白修,如果存在就報(bào)錯(cuò)。其實(shí)本博客的去重不太完美戒悠,首先內(nèi)存問題熬荆,一個(gè)12w個(gè)元素的大集合維護(hù)是個(gè)問題舟山,并且沒有持久化(當(dāng)然可以最開始绸狐,從mongo中讀出所有庫中已存在的uid),沒有考慮到分布式累盗。未來后期寒矿,去重應(yīng)該交給Redis這種緩存中間件,這邊只是演示作用若债。
第二個(gè)MongoPipeline符相,是進(jìn)行mongo儲(chǔ)存,細(xì)品一番~ 首先執(zhí)行類方法from_crawler蠢琳,從配置文件settings.py中讀取mongo庫的信息啊终,然后執(zhí)行__init__初始化信息,初始化實(shí)例屬性mongo_uri傲须、mongo_db 蓝牲。接著執(zhí)行open_spider,這個(gè)方法開啟一個(gè)爬蟲的時(shí)候會(huì)被執(zhí)行泰讽,生成真正的數(shù)據(jù)庫鏈接mongo_collection例衍。每次進(jìn)入管道,都會(huì)執(zhí)行process_item方法已卸,進(jìn)行數(shù)據(jù)插入操作佛玄。close_spider這個(gè)方法顧名思義,只有關(guān)閉一個(gè)爬蟲的時(shí)候會(huì)執(zhí)行累澡,將數(shù)據(jù)庫鏈接關(guān)閉梦抢。
去重和存庫的管道都介紹完了,下面查看一下配置信息愧哟,looksettings.py奥吩。
ITEM_PIPELINES = {
? 'beikejie.pipelines.DuplicatesPipeline': 299,
? 'beikejie.pipelines.MongoPipeline': 300
}
MONGO_URI = '127.0.0.1:27017'
MONGO_DATABASE = 'pjjlt'
復(fù)制代碼
ITEM_PIPELINES 就是開啟以上的兩個(gè)管道,數(shù)字越小翅雏,優(yōu)先級(jí)越高圈驼,所以去重優(yōu)先于存庫管道,符合邏輯望几。下面是數(shù)據(jù)庫配置信息绩脆。當(dāng)然,settings.py還有很多配置信息,有用到你可以自己看撒~
以上靴迫,我們實(shí)現(xiàn)了一個(gè)用戶主頁數(shù)據(jù)的讀取和儲(chǔ)存惕味。下面我們?nèi)プ龅诙胶偷谌健?/p>
06. 獲取關(guān)注者列表
繼續(xù)回到BeiKeJieSpider這個(gè)類,我們繼續(xù)爬取關(guān)注者列表玉锌。
?
可以看出名挥,關(guān)注者列表是分頁的,我們可以根據(jù)下一頁按鈕獲取下一頁url進(jìn)行翻頁操作主守。每頁關(guān)注者列表都可以獲取到它們的uid禀倔,從而我們又可以去拼湊出每個(gè)關(guān)注者的主頁url。獻(xiàn)上代碼~
cookies = {'你自己的cookie'}
? ? def parse(self, response):
? ? ? ? item = self.main_page_parse(response)
? ? ? ? yield item
? ? ? ? uid = item['uid']
? ? ? ? try:
? ? ? ? ? ? # # 點(diǎn)擊關(guān)注連接参淫,進(jìn)入關(guān)注頁救湖,爬取每個(gè)關(guān)注者的信息 http://www.tuilixy.net/home.php?mod=follow&uid=45001&do=following
? ? ? ? ? ? follower_url = f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=following'
? ? ? ? ? ? yield scrapy.Request(url=follower_url, cookies=self.cookies, callback=self.follower_parse)
? ? ? ? except Exception as e:
? ? ? ? ? ? logging.error("失敗:uid:"+uid+"\n錯(cuò)誤原因是: "+str(e))
? ? def follower_parse(self, response):
? ? ? ? logging.info('開始爬取關(guān)注列表,'+response.url)
? ? ? ? doc = pq(response.text)
? ? ? ? lis = doc('.flw_ulist.prw.plw').children('.ptf.pbf.cl')
? ? ? ? for li in lis:
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? url_end = pq(li)('.z.avt.w60.br4').attr('href')
? ? ? ? ? ? ? ? if url_end:
? ? ? ? ? ? ? ? ? ? url = 'http://www.tuilixy.net/'+url_end
? ? ? ? ? ? ? ? ? ? yield scrapy.Request(url=url, callback=self.parse)
? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? logging.error("爬取關(guān)注失斚巡拧:url:" + response.url +"\n錯(cuò)誤原因是: "+str(e))
? ? ? ? try:
? ? ? ? ? ? # 翻頁
? ? ? ? ? ? turn_page_url_start = doc('.nxt').attr('href')
? ? ? ? ? ? if turn_page_url_start:
? ? ? ? ? ? ? ? turn_page_url = 'http://www.tuilixy.net/'+turn_page_url_start
? ? ? ? ? ? ? ? yield scrapy.Request(url=turn_page_url, cookies=self.cookies, callback=self.follower_parse)
? ? ? ? except Exception as e:
? ? ? ? ? ? logging.error("爬取關(guān)注失斝取:url:" + response.url+"\n錯(cuò)誤原因是: "+str(e))
復(fù)制代碼
首先,補(bǔ)充下我們上面寫的parse方法耍铜,儲(chǔ)存完某用戶的信息后邑闺,爬取這個(gè)用戶的關(guān)注者列表的第一頁。獲取到第一頁關(guān)注著列表后執(zhí)行回調(diào)函數(shù)follower_parse棕兼。
follower_parse主要干了兩件事陡舅,獲取本頁所有用戶的uid,拼湊其用戶主頁url程储,并且執(zhí)行獲取用戶主頁的回調(diào)函數(shù)parse蹭沛。(就是去做我們需求分析的第一步)。第二件事就是章鲤,獲取下一頁url摊灭,進(jìn)行翻頁操作,獲取下一頁關(guān)注者列表败徊,并執(zhí)行獲取關(guān)注者列表的回調(diào)函數(shù)follower_parse帚呼。如此往復(fù),知道該用戶的關(guān)注者列表被翻到最后一步皱蹦。
請(qǐng)求關(guān)注者列表需要加入cookie煤杀,這個(gè)需要你自己從瀏覽器獲取(需要登錄,都爬人家了沪哺,不得注冊(cè)一個(gè)賬號(hào)嘛沈自,嗯哼?)辜妓。值得一提的是枯途,Scrapy的Request方法忌怎,設(shè)置cookie必須顯示設(shè)置,不能通過將cookie放到headers中酪夷!這個(gè)知識(shí)點(diǎn)消耗了小編好多時(shí)間找問題榴啸,害,還是太菜晚岭。鸥印。知道點(diǎn)開Request源碼。
?
以上坦报,我們就完成了某用戶的關(guān)注者列表的爬取库说。
07. 獲取粉絲列表
和爬取關(guān)注者邏輯一樣,直接上代碼吧燎竖。
? ? def parse(self, response):
? ? ? ? item = self.main_page_parse(response)
? ? ? ? yield item
? ? ? ? uid = item['uid']
? ? ? ? try:
? ? ? ? ? ? # # 點(diǎn)擊關(guān)注連接璃弄,進(jìn)入關(guān)注頁要销,爬取每個(gè)關(guān)注者的信息 http://www.tuilixy.net/home.php?mod=follow&uid=45001&do=following
? ? ? ? ? ? follower_url = f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=following'
? ? ? ? ? ? yield scrapy.Request(url=follower_url, cookies=self.cookies, callback=self.follower_parse)
? ? ? ? ? ? # # 點(diǎn)擊粉絲連接构回,進(jìn)入粉絲頁,爬取每個(gè)粉絲的信息 http://www.tuilixy.net/home.php?mod=follow&uid=45001&do=follower
? ? ? ? ? ? fans_url = f'http://www.tuilixy.net/home.php?mod=follow&uid={uid}&do=follower'
? ? ? ? ? ? yield scrapy.Request(url=fans_url, cookies=self.cookies, callback=self.fans_parse)
? ? ? ? except Exception as e:
? ? ? ? ? ? logging.error("失斒韪馈:uid:"+uid+"\n錯(cuò)誤原因是: "+str(e))
? ? def fans_parse(self, response):
? ? ? ? logging.info('開始爬取粉絲列表,'+response.url)
? ? ? ? doc = pq(response.text)
? ? ? ? lis = doc('.flw_ulist.prw.plw').children('.ptf.pbf.cl')
? ? ? ? for li in lis:
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? url_end = pq(li)('.z.avt.w60.br4').attr('href')
? ? ? ? ? ? ? ? if url_end:
? ? ? ? ? ? ? ? ? ? url = 'http://www.tuilixy.net/'+url_end
? ? ? ? ? ? ? ? ? ? yield scrapy.Request(url=url, callback=self.parse)
? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? logging.error("爬取粉絲失斚说А:url:" + response.url+"\n錯(cuò)誤原因是: "+str(e))
? ? ? ? # 翻頁
? ? ? ? try:
? ? ? ? ? ? turn_page_url_start = doc('.nxt').attr('href')
? ? ? ? ? ? if turn_page_url_start:
? ? ? ? ? ? ? ? turn_page_url = 'http://www.tuilixy.net/'+turn_page_url_start
? ? ? ? ? ? ? ? yield scrapy.Request(url=turn_page_url, cookies=self.cookies, callback=self.fans_parse)
? ? ? ? except Exception as e:
? ? ? ? ? ? logging.error("爬取粉絲失敗:url:" + response.url+"\n錯(cuò)誤原因是: "+str(e))
復(fù)制代碼
08. 運(yùn)行
邏輯寫完了浑塞,加上寫try借跪、except,還有關(guān)鍵性注釋酌壕,利用Scrapy命令行操作掏愁,開始跑吧。
scrapy crawl beikejie
復(fù)制代碼
crawl就是執(zhí)行一個(gè)爬蟲卵牍,后面的參數(shù)是爬蟲的name果港。
由于小編原始大V url只有一條,所以只爬了部分?jǐn)?shù)據(jù)糊昙,只有16429個(gè)用戶信息辛掠,大約耗時(shí)將近2個(gè)小時(shí)。你可以多選幾個(gè)大V释牺,選的越多萝衩,數(shù)據(jù)就會(huì)越無限接近12W。后期我們還可以利用分布式没咙,多開幾個(gè)scrapy實(shí)例猩谊,提高爬取速度。
?
09. 結(jié)束
相比于前面兩篇博客祭刚,這篇Scrapy數(shù)據(jù)量大牌捷,代碼編寫時(shí)間也較長(zhǎng)队塘,希望各位讀者小伙伴會(huì)喜歡~
喜歡的小伙伴,點(diǎn)個(gè)贊再走吧宜鸯,您的支持憔古,小編感激不盡。
最后多說一句淋袖,想學(xué)習(xí)Python可聯(lián)系小編鸿市,這里有我自己整理的整套python學(xué)習(xí)資料和路線,想要這些資料的都可以進(jìn)q裙930900780領(lǐng)取即碗。
本文章素材來源于網(wǎng)絡(luò)焰情,如有侵權(quán)請(qǐng)聯(lián)系刪除。