佳礼资讯网

 找回密码
 注册

ADVERTISEMENT

查看: 569|回复: 9

用Scrapy做網頁爬蟲

[复制链接]
发表于 20-4-2018 02:27 AM | 显示全部楼层 |阅读模式
本帖最后由 nsda 于 20-4-2018 09:17 PM 编辑

1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議


1. 簡介
Scrapy是什麼?
參考Scrapy官方網站是最準確的, 基本上Scrapy就是網頁爬蟲工具/框架,開源(https://github.com/scrapy/scrapy),用簡單的Python可以輕易爬幾千幾萬頁的網站。

為什麼要用Scrapy?
以前我嘗試自己寫過網頁爬蟲,分別用過Java/Scala/Python,但是每次都很難重用在其他爬蟲Projects(除非你肯花時間認真設計,並且抽象化所有接口)。
其次,網路上可以發生的事情太多,自己寫的爬蟲需要聰明的去retry以下的狀況:
- 可能你的電腦斷線,
- 可能對方網路不穩定,
- 可能雙方網路都沒問題不過就是Request Timeout,
- 也可能對方網站有Bugs,

自己寫的爬蟲也要到處去找適合的libraries來幫你extract HTML Elements,以前有時候懶惰找,自己寫Regex來當HTML Extractor,最後自己都看不懂自己的代碼

而Scrapy就是自動幫你完成這些麻煩的東西,簡單如以下的代碼可以爬完一個blog:
  1. # file name: myspider.py
  2. import scrapy

  3. class BlogSpider(scrapy.Spider):
  4.     name = 'blogspider'
  5.     start_urls = ['https://blog.scrapinghub.com']

  6.     def parse(self, response):
  7.     for title in response.css('h2.entry-title'):
  8.         yield {'title': title.css('a ::text').extract_first()}

  9.         for next_page in response.css('div.prev-post > a'):
  10.     yield response.follow(next_page, self.parse)
复制代码
  1. pip install scrapy # 安裝scrapy
  2. scrapy runspider myspider.py # 執行爬蟲
复制代码

當中你只需要懂url跟html elements的css selector,其他boilerplate代碼都大同小異的
回复

使用道具 举报


ADVERTISEMENT

发表于 20-4-2018 07:15 AM | 显示全部楼层
沙发来了
回复

使用道具 举报

 楼主| 发表于 20-4-2018 09:20 PM | 显示全部楼层
本帖最后由 nsda 于 20-4-2018 11:22 PM 编辑

1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議

安裝是沒什麼好寫的,因為用pip來安裝實在太容易了
  1. pip install scrapy
复制代码
注意:我的執行環境是python3,python2理應沒問題(讀取csv那邊可能要把'r'改成'rb'),不過我沒試過



回复

使用道具 举报

 楼主| 发表于 20-4-2018 09:29 PM | 显示全部楼层
1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議

這裡我打算用Scrapy來爬完佳禮國內政治版的所有回帖(希望這個帖不會被關

根據國內政治時事版的統計,這個版裡面有超過60000個主題,每個主題又有多個回帖(總共超過150萬),讓我們來看看用Scrapy抓完這些回帖有多難

我習慣開始寫Code前,先想一下策略
爬蟲就跟普通用戶一樣,它只能看到我們看到的,要怎樣爬完全部回帖?沒錯,跟人類一樣:
1. 去國內政治版首頁,看到25個主題
2. 按進去第一個主題
3. 看到回帖,全部抄起來
4. 如有主題內有下一頁,按下一頁,repeat #3, 如果沒了,退出當前主題
5. 按進下一個主題,repeat #3
6. 全部25個主題抄完了,按下一頁,repeat #2
一個月後,終於按完第2000頁了,你會發現下一頁的button按不到了,就這裡停下來

奇怪了,明明寫著有21xx頁數,為什麼只能按到2000頁?這是佳禮的bug,自己打2001頁時會回到第一頁,麻煩來了,頁數每天都在增加,我要怎樣盡量keep著舊主題? 我暫時想到的方法是,寫2個爬蟲:
爬蟲1 - 爬完全部主題,2000頁的主題很快就會爬完,然後存檔起來
爬蟲2 - 根據每個主題的url,爬完全部的回帖
以後即使舊主題被踢去超過2000頁,至少它們的ID還在我電腦,不至於找不到

回复

使用道具 举报

 楼主| 发表于 20-4-2018 10:20 PM | 显示全部楼层
本帖最后由 nsda 于 20-4-2018 11:25 PM 编辑

1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議


好了,有了對策,可以寫Code了

  1. import scrapy

  2. class ThreadSpider(scrapy.Spider):
  3.     name = "thread_spider"
  4.     start_urls = ['https://cforum1.cari.com.my/forum.php?mod=forumdisplay&fid=562']

  5.     def parse(self, response):
  6.         SELECTOR = "tbody[id^='normalthread_']"
  7.         for thread in response.css(SELECTOR):
  8.             NAME_SELECTOR = 'span.thaut ::text'
  9.             USER_ID_SELECTOR = 'tr td.avtx a ::attr(href)'
  10.             TITLE_SELECTOR = 'tr th div div a.xst ::text'
  11.             VIEW_SELECTOR = 'span.thview ::text'
  12.             COMMENTS_SELECTOR = 'span.thcmd ::text'
  13.             ID_SELECTOR = '::attr(id)'
  14.             yield {
  15.                     'id': int(thread.css(ID_SELECTOR).extract_first().replace('normalthread_', '')),
  16.                     'user_id': int(thread.css(USER_ID_SELECTOR).extract_first().replace('home.php?mod=space&uid=', '')),
  17.                     'user_name': thread.css(NAME_SELECTOR).extract_first(),
  18.                 'title': thread.css(TITLE_SELECTOR).extract_first(),
  19.                 'view': int(thread.css(VIEW_SELECTOR).extract_first().strip().replace('查看:', '')),
  20.                 'comments': int(thread.css(COMMENTS_SELECTOR).extract_first().strip().replace('评论:', '').replace('-', '0')),
  21.             }

  22.         NEXT_PAGE_SELECTOR = 'a.nxt ::attr(href)'
  23.         next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()
  24.         if next_page:
  25.             yield scrapy.Request(
  26.                 response.urljoin(next_page),
  27.                 callback=self.parse
  28.             )

复制代码

start_urls裡的https://cforum1.cari.com.my/forum.php?mod=forumdisplay&fid=562就是國內政治時事的url所以的selector都是css selector,如果你在chrome,用view page source會大概明白我在做什麼

以上圖為例,我先用“SELECTOR = "tbody[id^='normalthread_']"”來找單一主題,這個selector知道每個"normalthread_"為ID開頭的tbody都是一個主題。

然後用“TITLE_SELECTOR = 'tr th div div a.xst ::text'”來找主題的題目,仔細看你會發現,我只是沿著tbody->tr->th->div->a.xst一直找到::text,然後用extract_first來抓第一個符合的value(通常都是,depends on html design)


一些value我拿到後會做些filtering,比如每個主題都有“查看:xxx”,我只要xxx並且要收成數字,所以用了replace跟int


  1. import scrapy
  2. import urllib.parse as urlparse
  3. import csv
  4. import os

  5. class PostSpider(scrapy.Spider):
  6.     name = "post_spider"
  7.     file_timestamp = os.getenv('TIMESTAMP')
  8.     threads_file = 'threads.csv'
  9.    
  10.     if file_timestamp:
  11.         threads_file = 'threads_' + file_timestamp + '.csv'
  12.         
  13.     data_562 = []
  14.     with open(threads_file, 'r') as f:
  15.         reader = csv.DictReader(f)
  16.         data_562 = list(reader)
  17.     ids_562 = [d['id'] for d in data_562]
  18.     ids = list(set(ids_562)) # avoid duplicate ids
  19.     print(ids)

  20.     start_urls = ["https://cforum1.cari.com.my/forum.php?mod=viewthread&tid={}".format(id) for id in ids]

  21.     def parse(self, response):
  22.         POST = "div[id^='post_'] table[id^='pid']"
  23.         for post in response.css(POST):
  24.             POST_ID = '::attr(id)'
  25.             USER_ID = 'div.favatar div.pi div.authi a ::attr(href)'
  26.             USER_NAME = 'div.favatar div.pi div.authi a ::text'
  27.             CREATED_AT = 'em[id^=authorposton] ::text'
  28.             ORDER = "a[id^=postnum] em ::text"
  29.             yield {
  30.                 'thread_id': int(urlparse.parse_qs(urlparse.urlparse(response.url).query)['tid'][0]),
  31.                 'post_id': int(post.css(POST_ID).extract_first().replace('pid', '')),
  32.                     'user_id': int(post.css(USER_ID).extract_first().replace('home.php?mod=space&uid=', '')),
  33.                     'user_name': post.css(USER_NAME).extract_first(),
  34.                 'order': int(post.css(ORDER).extract_first()),
  35.                 'created_at': post.css(CREATED_AT).extract_first().replace('发表于 ', ''),
  36.             }

  37.         NEXT_PAGE_SELECTOR = 'a.nxt ::attr(href)'
  38.         next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()
  39.         if next_page:
  40.             yield scrapy.Request(
  41.                 response.urljoin(next_page),
  42.                 callback=self.parse
  43.             )

复制代码

這裡跟上面大致一樣,只是多了一個讀取url的步驟,第一個爬蟲是從第一頁開始,但是這個爬蟲是根據第一個爬蟲的結果,所以需要這個步驟。


可能你會奇怪,為什麼會有file_timestamp這個東西,這是拿來方便做automation的





回复

使用道具 举报

 楼主| 发表于 20-4-2018 10:21 PM | 显示全部楼层
本帖最后由 nsda 于 20-4-2018 11:01 PM 编辑

1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議

好了,可以開始執行(確定你已經安裝了Scrapy):
  1. scrapy runspider threads.py -o threads.csv -s "DEPTH_LIMIT=2"
复制代码



執行完畢,你會看到一些statistics:

  1. {'downloader/request_bytes': 1780,
  2. 'downloader/request_count': 3,
  3. 'downloader/request_method_count/GET': 3,
  4. 'downloader/response_bytes': 144564,
  5. 'downloader/response_count': 3,
  6. 'downloader/response_status_count/200': 3,
  7. 'finish_reason': 'finished',
  8. 'finish_time': datetime.datetime(2018, 4, 20, 13, 34, 18, 825890),
  9. 'item_scraped_count': 85,
  10. 'log_count/DEBUG': 90,
  11. 'log_count/INFO': 8,
  12. 'memusage/max': 49709056,
  13. 'memusage/startup': 49704960,
  14. 'request_depth_max': 2,
  15. 'response_received_count': 3,
  16. 'scheduler/dequeued': 3,
  17. 'scheduler/dequeued/memory': 3,
  18. 'scheduler/enqueued': 3,
  19. 'scheduler/enqueued/memory': 3,
  20. 'start_time': datetime.datetime(2018, 4, 20, 13, 34, 14, 529640)}
复制代码


而results也會存在一個叫threads.csv的文件

  1. $ head -n10 threads.csv
  2. id,user_id,user_name,title,view,comments
  3. 4049311,2136456,Cari_YWQ,“被人叫走狗” 陈胜尧感叹只是道出真相,到底做错了什么?,620,45
  4. 4049151,273449,蓝火爆,投希盟的理由!!!,1215,76
  5. 4049112,2236753,一燈大師,繼『兩千塊拿你命』後,現在升級到策劃『自殺式攻擊』了,826,40
  6. 4049393,1580059,abc74,国阵候选人名单难产,选情吃紧打乱纳吉布局?,33,1
  7. 4048519,2306283,華沙論賤,可以投票了!來透露你 509 當天 囯席 會投給哪個陣營?,1103,34
  8. 4049229,173051,行动人,PACABA希盟上台的最后防线。,274,18
  9. 4043080,2236753,一燈大師,希盟的票房毒藥,1218,58
  10. 4047879,1997663,顺水,感谢东马联邦朋友,要不是你们反风不强,现在半岛......,1102,69
  11. 4049386,2183919,Cari_LSV,网友接到诈骗电话  一秒识破还把对方气得急挂电话!,125,17
复制代码


第二個爬蟲,它需要threads.csv來執行:
  1. scrapy runspider posts.py -o posts.csv
复制代码


一樣當執行完畢,你會看到一些statistics:

  1. {'downloader/request_bytes': 194009,
  2. 'downloader/request_count': 257,
  3. 'downloader/request_method_count/GET': 257,
  4. 'downloader/response_bytes': 12838667,
  5. 'downloader/response_count': 257,
  6. 'downloader/response_status_count/200': 257,
  7. 'finish_reason': 'shutdown',
  8. 'finish_time': datetime.datetime(2018, 4, 20, 14, 2, 8, 591056),
  9. 'item_scraped_count': 4237,
  10. 'log_count/DEBUG': 4495,
  11. 'log_count/ERROR': 1,
  12. 'log_count/INFO': 9,
  13. 'memusage/max': 49569792,
  14. 'memusage/startup': 49569792,
  15. 'request_depth_max': 15,
  16. 'response_received_count': 257,
  17. 'scheduler/dequeued': 257,
  18. 'scheduler/dequeued/memory': 257,
  19. 'scheduler/enqueued': 258,
  20. 'scheduler/enqueued/memory': 258,
  21. 'spider_exceptions/TypeError': 1,
  22. 'start_time': datetime.datetime(2018, 4, 20, 14, 1, 20, 871405)}
复制代码


這裡你會看到finish_reason是shutdown,因為我沒等它跑完就按Ctrl+C來終止了,依然會給你看report。

而results也會存在一個叫posts.csv的文件

  1. thread_id,post_id,user_id,user_name,order,created_at
  2. 4048618,132260179,2306283,華沙論賤,1,18-4-2018 06:11 AM
  3. 4048618,132260184,2249107,Cinabaku,2,18-4-2018 06:15 AM
  4. 4048618,132260189,2306283,華沙論賤,3,18-4-2018 06:25 AM
  5. 4048618,132261005,1843869,步惊云义侠,4,18-4-2018 10:22 AM
  6. 4048618,132261046,2065200,+_o_+,5,18-4-2018 10:30 AM
  7. 4048618,132261604,1844841,liau1977,6,18-4-2018 12:10 PM
  8. 4048618,132261695,1730941,Weida,7,18-4-2018 12:25 PM
  9. 4049164,132269109,1126865,tunrazak,1,20-4-2018 12:57 AM
  10. 4048742,132262476,2222531,snsv,1,18-4-2018 02:57 PM
复制代码


好了,為什麼我要放DEPTH_LIMIT呢?這裡告訴爬蟲我只要另外2層的數據,即使到第三頁就會停止(第二頁算是第一層,以此類推...),如果你拿掉DEPTH_LIMIT,爬蟲會一直執行直到無法再執行(第2000頁)。

完成了?還沒!


每次要跑二個爬蟲也很麻煩,最方便就是寫個script (run_spider.sh):

  1. #!/bin/bash

  2. set -e

  3. TIMESTAMP=$(date +%s)
  4. LIMIT=${1:-2000}

  5. scrapy runspider threads.py -o threads_$TIMESTAMP.csv -s "DEPTH_LIMIT=$LIMIT"
  6. scrapy runspider posts.py -o posts_$TIMESTAMP.csv

  7. echo
  8. echo "Threads data has been saved to threads_$TIMESTAMP.csv"
  9. echo "Posts data has been saved to posts_$TIMESTAMP.csv"
复制代码


記得給它執行的權限
  1. chmod +x run_spider.sh
复制代码


執行
  1. ./run_scrapy.sh
复制代码


跑完後你會看見類似以下的訊息:

  1. Threads data has been saved to threads_1524233820.csv
  2. Posts data has been saved to posts_1524233820.csv
复制代码






回复

使用道具 举报

Follow Us
 楼主| 发表于 20-4-2018 10:21 PM | 显示全部楼层
本帖最后由 nsda 于 20-4-2018 11:21 PM 编辑

1. 簡介
2. 安裝
3. 用Scrapy爬佳禮的帖
   - 策劃
   - coding
   - 執行
   - 建議

從之前的代碼,你可以看到爬完整個國內版都不需要用到Regex,方便日後修改。
第二個爬蟲我沒有抓取回帖內容,因為內容佔很多空間,需要內容的可以自己嘗試修改,當作練習,有問題可以膠流膠流

以下是一些跑完爬蟲後到建議:
1. 不需要每次都跑完全部頁數,比如二個星期後你打算更新data,跑前面100頁已經卓卓有餘了,節省自己的時間也減輕對方server的負擔
- 可以用DEPTH_LIMIT做到這點
- 或者用我已經寫好的shell script
  1. ./run_scrapy.sh 100
复制代码

2. Scrapy可以同時進行的,但是這對網站來說也是一個額外的負擔,建議不要用,盡量做個文明的爬蟲
3. 爬完全部主題只需要少過一個小時的時間,但是爬完全部回帖需要幾天,所以:
- 建議申請一個aws ec2 free tier來跑,最好選新加坡region,連結到馬來西亞的佳禮server比較快,需要配合Screen command(http://dasunhegoda.com/unix-screen-command/263/),不然一logout爬蟲就完蛋了
- 或者把手上擁有的主題分批跑,這需要改以上的python script
- 佳禮每個星期1/星期4點早上凌晨維修,跑爬蟲時注意這點


最後,這篇只是Scrapy的教學,如果你需要以上爬蟲所爬到的數據(超過150萬),我願意分享出來,所以佳禮不用被虐待

我也是Scrapy新手,這篇教學其實也很基礎,如果你有其他更複雜的網頁,歡迎一起研究
回复

使用道具 举报

 楼主| 发表于 20-4-2018 11:23 PM | 显示全部楼层

歡迎當爬蟲
回复

使用道具 举报


ADVERTISEMENT

发表于 30-5-2018 11:17 AM | 显示全部楼层

可以拿来开发打水跟单软件
希望有机会交流
回复

使用道具 举报

 楼主| 发表于 30-5-2018 02:58 PM | 显示全部楼层
槟城路透社 发表于 30-5-2018 11:17 AM
可以拿来开发打水跟单软件
希望有机会交流

歡迎交流 最近拿完假又沒時間玩其他東西了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

 

ADVERTISEMENT



ADVERTISEMENT



ADVERTISEMENT

ADVERTISEMENT


版权所有 © 1996-2023 Cari Internet Sdn Bhd (483575-W)|IPSERVERONE 提供云主机|广告刊登|关于我们|私隐权|免控|投诉|联络|脸书|佳礼资讯网

GMT+8, 29-3-2024 01:20 AM , Processed in 0.067882 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表