Python爬蟲入門(urllib+Beautifulsoup)

Python爬蟲入門(urllib+Beautifulsoup)

本文包括:

1、爬蟲簡單介紹

2、爬蟲架構(gòu)三大模塊

3、urllib

4、BeautifulSoup

5、實(shí)戰(zhàn)演練:爬取百度百科1000個(gè)頁面

1、爬蟲簡單介紹

  • 爬蟲:一段自動(dòng)抓取互聯(lián)網(wǎng)信息的程序

  • 從一個(gè)url出發(fā),然后訪問和這個(gè)url相關(guān)的各種url,并提取相關(guān)的價(jià)值數(shù)據(jù)。

  • URL:Uniform Resource Location的縮寫,譯為“統(tǒng)一資源定位符”

  • URL的格式由下列三部分組成:

    • 第一部分是協(xié)議(或稱為服務(wù)方式);
    • 第二部分是存有該資源的主機(jī)IP地址(有時(shí)也包括端口號);
    • 第三部分是主機(jī)資源的具體地址,如目錄和文件名等。

2、爬蟲架構(gòu)三大模塊

  • URL 管理器

    • 管理待抓取URL集合和已抓取URL集合

    • 防止重復(fù)抓取、防止循環(huán)抓取

    • 邏輯:

      1.判斷待添加URL是否在容器中

      2.添加新URL到待爬取集合

      3.判斷是否有待爬取URL

      4.獲取待爬取URL

      5.將URL從待爬取移動(dòng)至已爬取

    • URL管理器的實(shí)現(xiàn)方式有三種:

      1、適合個(gè)人的:內(nèi)存

      2、小型企業(yè)或個(gè)人:關(guān)系數(shù)據(jù)庫(永久存儲或內(nèi)存不夠用,如 MySQL)

      3、大型互聯(lián)網(wǎng)公司:緩存數(shù)據(jù)庫(高性能,如支持 set 的 redis)

  • 網(wǎng)絡(luò)下載器

    • 將給定的URL網(wǎng)頁內(nèi)容下載到本地,以便后續(xù)操作

    • 常見網(wǎng)絡(luò)下載器:

      • urllib2:Python 官方基礎(chǔ)模塊

      • requests:第三方

        注意:python 3.x中 urllib 庫和 urilib2 庫合并成了urllib 庫。其中 urllib2.urlopen() 變成了urllib.request.urlopen()。urllib2.Request() 變成了 urllib.request.Request()

    • 特殊情境處理(4種 handler):

      1.需要用戶登錄才能訪問(HTTPCookieProcessor)

      2.需要代理才能訪問(ProxyHandler)

      3.協(xié)議使用HTTPS加密訪問(HTTPSHandler)

      4.URL自動(dòng)跳轉(zhuǎn)(HTTPRedirectHandler)

    • 4種方法下載網(wǎng)頁的實(shí)例(基于 Python3.6)

      見下一節(jié):urllib庫

  • 網(wǎng)絡(luò)解析器

    • 通過解析得到想要的內(nèi)容,解析出新的 url 交給 URL 管理器,形成循環(huán)
    • 正則表達(dá)式:模糊匹配
    • beautifulsoup:第三方,可使用 html.parser 和 lxml 作為解析器,結(jié)構(gòu)化解析(DOM 樹)
    • html.parser
    • lxml

3、urllib

  • 4種方法下載網(wǎng)頁的實(shí)例(基于 Python3.6)

          import urllib.request
          import http.cookiejar
          url = 'https://baidu.com'
    
          print('urllib下載網(wǎng)頁方法1:最簡潔方法')
          # 直接請求
          res = urllib.request.urlopen(url)
          # 獲取狀態(tài)碼,如果是200則獲取成功
          print(res.getcode())
          # 讀取內(nèi)容 #cont是很長的字符串就不輸出了
          cont = res.read().decode('utf-8')
    
          print('urllib下載網(wǎng)頁方法2:添加data、http header')
          # 創(chuàng)建Request對象
          request = urllib.request.Request(url)
          # 添加數(shù)據(jù)
          request.data = 'a'
          # 添加http的header #將爬蟲偽裝成Mozilla瀏覽器
          request.add_header('User-Agent', 'Mozilla/5.0')
          # 添加http的header #指定源網(wǎng)頁,防止反爬
          request.add_header('Origin', 'https://xxxx.com')
          # 發(fā)送請求獲取結(jié)果
          response = urllib.request.urlopen(request)
    
          print('urllib下載網(wǎng)頁方法3:添加特殊情景的處理器')
          # 創(chuàng)建cookie容器
          cj = http.cookiejar.CookieJar()
          # 創(chuàng)建一個(gè)opener
          opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
          # 給urllib安裝opener
          urllib.request.install_opener(opener)
          # 使用帶有cookie的urllib訪問網(wǎng)頁
          response = urllib.request.urlopen(url)
    
          # 使用 post 提交數(shù)據(jù)
          from urllib import parse
          from urllib.request import Request
          from urllib.request import urlopen
          req = Request(url)
          postData = parse.urlencode([
              (key1, value1),
              (key2, value2),
              ...
          ])
          urlopen(req, data=postData.encode('utf-8'))
    

4、BeautifulSoup

  • 文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/

  • BeautifulSoup語法:

    根據(jù)一個(gè)HTML網(wǎng)頁字符串創(chuàng)建BeautifulSoup對象,創(chuàng)建的同時(shí)就將整個(gè)文檔字符串下載成一個(gè)DOM樹,后根據(jù)這個(gè)DOM樹搜索節(jié)點(diǎn)。find_all方法搜索出所有滿足的節(jié)點(diǎn),find方法只會搜索出第一個(gè)滿足的節(jié)點(diǎn),兩方法參數(shù)一致。搜索出節(jié)點(diǎn)后就可以訪問節(jié)點(diǎn)的名稱、屬性、文字。因此在搜索時(shí)也可以按照以上三項(xiàng)搜索。

  • 實(shí)例:

      from bs4 import BeautifulSoup
      # 第一步:根據(jù)HTML網(wǎng)頁字符串創(chuàng)建BeautifulSoup對象
      soup = BeautifulSoup(
                      'XX.html',            # HTML文檔字符串
                      'html.parser'         # HTML解析器
                      from_encoding='utf8'  # HTML文檔的編碼
                      )
      # 第二步:搜索節(jié)點(diǎn)(find_all,find)
      # 方法:find_all(name,attrs,string)    # 名稱,屬性,文字
      # 查找所有標(biāo)簽為a的標(biāo)簽
      soup.find_all(‘a(chǎn)’)
      # 查找第一個(gè)標(biāo)簽為a的標(biāo)簽
      soup.find(‘a(chǎn)’)
      # 查找所有標(biāo)簽為a,鏈接符合'/view/123.html'形式的節(jié)點(diǎn)
      soup.find_all('a',href='/view/123.html')
      # 查找所有標(biāo)簽為div,class為abc,文字為python的節(jié)點(diǎn)
      soup.find_all('div',class_='abc',string='python')
      # class 是 Python 保留關(guān)建字,所以為了區(qū)別加了下劃線
    
      # 以下三種方式等價(jià)
      # soup.h1 = soup.html.body.h1 = soup.html.h1
    
      # 最強(qiáng)大的是利用正則表達(dá)式
      import re
      soup.find('a', href=re.compile(r"view"))
      soup.find_all("img", {"src":re.compile("xxx")})
    
      # 第三步:訪問節(jié)點(diǎn)信息
      # 得到節(jié)點(diǎn):<a href=‘1.html’>Python</a>
      node = soup.find(‘a(chǎn)’)
      # 獲取查找到的節(jié)點(diǎn)的標(biāo)簽名稱
      print(node.name)
      # 獲取查找的a節(jié)點(diǎn)的href屬性
      print(node['href'])
      # 獲取查找到的a節(jié)點(diǎn)的文本字符串
      print(node.get_text())
    

    findAll() 、find()函數(shù)詳解:
    findAll(tag, attributes, recursive, text, limit, keywords)。
    find(tag, attributes, recursive, text, keywords)

    tag 可以傳入一個(gè)標(biāo)簽的名稱或多個(gè)標(biāo)簽名組成的Python列表做標(biāo)簽參數(shù)

    attributes是用一個(gè)Python字典封裝一個(gè)標(biāo)簽的若干屬性和對應(yīng)的屬性值

    recursive是布爾變量,默認(rèn)為True,如果為False,findAll就只查找文檔的一級標(biāo)簽

    text用標(biāo)簽的文本內(nèi)容去匹配,而不是標(biāo)簽的屬性

    limit 只能用于findAll,find其實(shí)就是findAll的limit=1的特殊情況

    keyword 有點(diǎn)冗余,不管了

    95%的時(shí)間都只用到了tag、attributes。

  • 導(dǎo)航樹

    • 子標(biāo)簽children與后代標(biāo)簽descendants:

      # 匹配標(biāo)簽的的下一級標(biāo)簽
      bsobj.find("table", {"id":"giftlist"}).children
      
      # 匹配標(biāo)簽的所有后代標(biāo)簽,包括一大堆亂七八糟的img,span等等
      bsobj.find("table", {"id":"giftlist"}).descendants
      
    • 兄弟標(biāo)簽next_siblings/previous_siblings:

      # 匹配標(biāo)簽的后一個(gè)標(biāo)簽
      bsobj.find("table", {"id":"giftlist"}).tr.next_siblings
      
      # 匹配標(biāo)簽的前一個(gè)標(biāo)簽(如果同級標(biāo)簽的最后一個(gè)標(biāo)簽容易定位,那么就用這個(gè))
      bsobj.find("table", {"id":"giftlist"}).tr.previous_siblings
      
    • 父標(biāo)簽parent、parents:

      # 選取table標(biāo)簽本身(這個(gè)操作多此一舉,只是為了舉例層級關(guān)系)
      bsobj.find("table", {"id":"giftlist"}).tr.parent
      
  • 獲取屬性

    • 如果我們得到了一個(gè)標(biāo)簽對象,可以用下面的代碼獲得它的全部標(biāo)簽屬性:

      mytag.attrs
      
    • 注意:這行代碼返回的是一個(gè)字典對象,所以可以獲取任意一個(gè)屬性值,例如獲取src屬性值就這樣寫代碼:

      mytag.attrs["src"]
      
  • 編寫可靠的代碼(捕捉異常)

    • 讓我們看看爬蟲import語句后面的第一行代碼,如何處理那里可能出現(xiàn)的異常:

      html = urlopen("http://www.pythonscraping.com/pages/page1.html")
      

      這有可能會報(bào)404錯(cuò)誤,所以應(yīng)該捕捉異常:

      try:
          html = urlopen("http://www.pythonscraping.com/pages/page1.html")
      except HTTPError as e:
          print(e)     # 返回空值,中斷程序,或者執(zhí)行另一個(gè)方案
      else:
      # 程序繼續(xù)。注意:如果你已經(jīng)在上面異常捕捉那一段代碼里返回或中斷(break),
      # 那么就不需要使用else語句了,這段代碼也不會執(zhí)行
      
    • 下面這行代碼(nonExistentTag是虛擬的標(biāo)簽,BeautifulSoup對象里實(shí)際沒有)

      print(bsObj.nonExistentTag)
      

      會返回一個(gè)None對象。處理和檢查這個(gè)對象是十分必要的。如果你不檢查,直接調(diào)用這個(gè)None對象的子標(biāo)簽,麻煩就來了。如下所示。

      print(bsObj.nonExistentTag.someTag)
      

      這時(shí)就會返回一個(gè)異常:

      AttributeError: 'NoneType' object has no attribute 'someTag'
      

      那么我們怎么才能避免這兩種情形的異常呢?最簡單的方式就是對兩種情形進(jìn)行檢查:

      try:
          badContent = bsObj.nonExistingTag.anotherTag
      exceptAttributeError as e:
          print("Tag was not found")
      else:
          if badContent == None:
              print ("Tag was not found")
          else:
              print(badContent)
      

5、實(shí)戰(zhàn)演練:爬取百度百科1000個(gè)頁面

  • 步驟

    1. 確定目標(biāo):確定抓取哪個(gè)網(wǎng)站的哪些網(wǎng)頁的哪部分?jǐn)?shù)據(jù)。本實(shí)例確定抓取百度百科python詞條頁面以及它相關(guān)的詞條頁面的標(biāo)題和簡介。
    2. 分析目標(biāo):確定抓取數(shù)據(jù)的策略。一是分析要抓取的目標(biāo)頁面的URL格式,用來限定要抓取的頁面的范圍;二是分析要抓取的數(shù)據(jù)的格式,在本實(shí)例中就是要分析每一個(gè)詞條頁面中標(biāo)題和簡介所在的標(biāo)簽的格式;三是分析頁面的編碼,在網(wǎng)頁解析器中指定網(wǎng)頁編碼,才能正確解析。
    3. 編寫代碼:在解析器中會使用到分析目標(biāo)步驟所得到的抓取策略的結(jié)果。
    4. 執(zhí)行爬蟲。
  • 確定框架

    20171031-baike
    • spider_main.py 是爬蟲主體
    • url_manager.py 維護(hù)了兩個(gè)集合,用來記錄要爬取的 url 和已爬取的 url
    • html_downloader.py 調(diào)用了 urllib 庫來下載 html 文檔
    • html_parser.py 調(diào)用了 BeautifulSoup 來解析 html 文檔
    • html_outputer.py 把解析后的數(shù)據(jù)存儲起來,寫入 output.html 文檔中
  • url_manager

      class UrlManager(object):
          def __init__(self):
              # 初始化兩個(gè)集合
              self.new_urls = set()
              self.old_urls = set()
    
          def add_new_url(self, url):
              if url is None:
                  return
              if url not in self.new_urls or self.old_urls:
                  # 防止重復(fù)爬取
                  self.new_urls.add(url)
    
          def add_new_urls(self, urls):
              if urls is None or len(urls) == 0:
                  return
              for url in urls:
                  # 調(diào)用子程序
                  self.add_new_url(url)
    
          def has_new_url(self):
              return len(self.new_urls) != 0
    
          def get_new_url(self):
              new_url = self.new_urls.pop()
              self.old_urls.add(new_url)
              return new_url
    

    解釋:管理器維護(hù)了兩個(gè)集合(new_urls、old_urls),分別記錄要爬和已爬 url,注意到前兩個(gè) add 方法,一個(gè)是針對單個(gè) url,一個(gè)是針對 url 集合,不要忘記去重操作。

  • html_downloader

      # coding:utf-8
      import urllib.request
    
      class HtmlDownloader(object):
          def download(self, url):
              if url is None:
                  return None
              response = urllib.request.urlopen(url)
              if response.getcode() != 200:  # 判斷是否請求成功
                  return None
              return response.read()
    

    解釋:很直觀的下載,這是最簡單的做法

  • html_parser

      from bs4 import BeautifulSoup
      import urllib.parse
      import re
    
    
      class HtmlParser(object):
    
          def _get_new_urls(self, page_url, soup):
              new_urls = set()
              links = soup.find_all('a', href = re.compile(r"/item/"))
              for link in links:
                  new_url = link['href']
                  new_full_url = urllib.parse.urljoin(page_url, new_url)
                  new_urls.add(new_full_url)
              return new_urls
    
          def _get_new_data(self, page_url, soup):
              res_data = {}
              res_data['url'] = page_url
              title_node = soup.find('dd', class_="lemmaWgt-lemmaTitle-title").find("h1")
              res_data['title'] = title_node.get_text()
              summary_node = soup.find('div', class_="lemma-summary")
              res_data['summary'] = summary_node.get_text()
              return res_data
    
          def parse(self, page_url, html_cont):
              if page_url is None or html_cont is None:
                  return
              soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
              new_urls = self._get_new_urls(page_url, soup)
              new_data = self._get_new_data(page_url, soup)
              return new_urls, new_data
    

    解釋:在解析器中,注意到 parse 方法,它從 html 文檔中找到所有詞條鏈接,并將它們包裝到 new_urls 集合中,最后返回,同時(shí),它還會解析出 new_data 集合,這個(gè)集合存放了詞條的名字(title)以及摘要(summary)。

  • spider_main

      # coding:utf-8
      from baike_spider import url_manager, html_downloader, html_parser, html_outputer
      import logging
    
      class SpiderMain(object):
          def __init__(self):
              self.urls = url_manager.UrlManager()
              self.downloader = html_downloader.HtmlDownloader()
              self.parser = html_parser.HtmlParser()
              self.outputer = html_outputer.HtmlOutputer()
    
          def crawl(self, root_url):
              count = 1 # record the current number url
              self.urls.add_new_url(root_url)
              while self.urls.has_new_url():
                  try:
                      new_url = self.urls.get_new_url()
                      print('crawl No.%d: %s'%(count, new_url))
                      html_cont = self.downloader.download(new_url)
                      new_urls, new_data = self.parser.parse(new_url, html_cont)
                      self.urls.add_new_urls(new_urls)
                      self.outputer.collect_data(new_data)
                      if count == 1000:
                          break
                      count += 1
                  except:
                      logging.warning('crawl failed')
              self.outputer.output_html()
    
    
      if __name__ == "__main__":
          root_url = "https://baike.baidu.com/item/Python/407313"
          obj_spider = SpiderMain()
          obj_spider.crawl(root_url)
    

    解釋:主程序?qū)?“Python” 的詞條頁面進(jìn)入,然后開始爬取數(shù)據(jù)。注意到,每爬取一個(gè)頁面,都有可能有新的 url 被解析出來,所以要交給 url_manager 管理,然后將 new_data 收集起來,當(dāng)跳出 while 循環(huán)時(shí),將數(shù)據(jù)輸出(因數(shù)據(jù)量不大,直接存放在內(nèi)存中)。

  • html_outputer

      class HtmlOutputer(object):
          def __init__(self):
              self.datas = []  # 列表
    
          def collect_data(self, data):
              if data is None:
                  return
              self.datas.append(data)
    
          def output_html(self):
              with open('output.html', 'w', encoding='utf-8') as fout:
                  fout.write("<html>")
                  fout.write("<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"></head>")
                  fout.write("<body>")
                  fout.write("<table>")
    
                  for data in self.datas:
                      fout.write("<tr>")
                      fout.write("<td>%s</td>" % data["url"])
                      fout.write("<td>%s</td>" % data["title"])
                      fout.write("<td>%s</td>" % data["summary"])
                      fout.write("</tr>")
    
                  fout.write("</table>")
                  fout.write("</body>")
                  fout.write("</html>")
    

    解釋:注意編碼問題就好

  • 輸出:

      "C:\Program Files\Python36\python.exe" D:/PythonProject/immoc/baike_spider/spider_main.py
      crawl No.1: https://baike.baidu.com/item/Python/407313
      crawl No.2: https://baike.baidu.com/item/Zope
      crawl No.3: https://baike.baidu.com/item/OpenCV
      crawl No.4: https://baike.baidu.com/item/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F
      crawl No.5: https://baike.baidu.com/item/JIT
      crawl No.6: https://baike.baidu.com/item/%E9%9C%80%E6%B1%82%E9%87%8F
      crawl No.7: https://baike.baidu.com/item/Linux
      crawl No.8: https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B
      crawl No.9: https://baike.baidu.com/item/Pylons
      crawl No.10: https://baike.baidu.com/item/%E4%BA%A7%E5%93%81%E8%AE%BE%E8%AE%A1
    
      Process finished with exit code 0
    
  • output.html

    20171031-baikeout

本篇內(nèi)容來自慕課網(wǎng)視頻教程:http://www.imooc.com/learn/563

爬取百度百科的源碼地址:https://github.com/edisonleolhl/imooc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內(nèi)容