Python爬蟲學習(一)

其實node.js也可以做爬蟲,相信前端的同學已經(jīng)有所了解困食,但今天我們要講的是如何用python笼蛛,python實現(xiàn)起來其實更簡單趟妥。

import urllib.request

url = "http://www.baidu.com"
response = urllib.request.urlopen(url).read()
data = data.decode('UTF-8')
print(data)   //data是html標簽內(nèi)容

urllib是python里面一個處理urls的庫祟敛,可以看教程,這里簡單介紹一下疤坝。

import urllib.request
with urllib.request.urlopen('http://www.python.org/') as f:
       print(f.read(300))

This example gets the python.org main page and displays the first 300 bytes of it.
上面的例子展示了讀取該網(wǎng)頁的前300個字節(jié)兆解。
除了可以向urlopen方法里面?zhèn)鬟f網(wǎng)頁地址馆铁,還可以構造一個request對象。

import urllib.request
DATA = b'some data'
// 構造一個request請求對象锅睛,包括請求地址埠巨,參數(shù)和請求方法
req = urllib.request.Request(url='http://localhost:8080', data=DATA,method='PUT')
with urllib.request.urlopen(req) as f:
    pass

我們來看看下面這段打印提示

a = urllib.request.urlopen(full_url)
type(a)
<class ‘http.client.HTTPResponse’>
 
 a.geturl()
‘http://www.baidu.com/s?word=Jecvay’
 
a.info()
<http.client.HTTPMessage object at 0x03272250>
 
 a.getcode()
200

以上可以看出構造出的a都包含哪些內(nèi)容。

如果要抓取百度上面搜索關鍵詞kobe bryant的網(wǎng)頁, 則代碼如下

import urllib
import urllib.request
 
data={}
data['word']='kobe bryant'
 
url_values=urllib.parse.urlencode(data)
url="http://www.baidu.com/s?"
full_url=url+url_values
 
data=urllib.request.urlopen(full_url).read()
data=data.decode('UTF-8')
print(data)

我們看下urllib.parse這個模塊的基本用法

urlencode函數(shù)现拒,可以把key-value這樣的鍵值對轉換成我們想要的格式辣垒,返回的是a=1&b=2這樣的字符串,比如:
from urllib import urlencode
 data = {
     'a': 'test',
     'name': '魔獸'
 }
 print urlencode(data)
a=test&name=%C4%A7%CA%DE

from urllib.parse import urlparse
    o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
    o   
    ParseResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html',
            params='', query='', fragment='')
   o.scheme   'http'
   o.port    80
   o.geturl()    'http://www.cwi.nl:80/%7Eguido/Python.html'

在寫代碼之前我們需要先了解一下python中隊列的知識印蔬。

from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry")           # Terry 入隊
queue.append("Graham")          # Graham 入隊
queue.popleft()                 # 隊首元素出隊
#輸出: 'Eric'
queue.popleft()                 # 隊首元素出隊
#輸出: 'John'
queue                           # 隊列中剩下的元素
#輸出: deque(['Michael', 'Terry', 'Graham'])
List用來完成隊列功能其實是低效率的, 因為List在隊首使用 pop(0) 和 insert() 
都是效率比較低的, Python官方建議使用collection.deque來高效的完成隊列任務

好了介紹完基本知識勋桶,就開始寫代碼了,畢竟我是一個coder...

import re
import urllib.request
import urllib
 
from collections import deque
 
queue = deque()
visited = set()
 
url = 'http://news.dbanotes.net'  # 入口頁面, 可以換成別的
 
queue.append(url)
cnt = 0
 
while queue:
  url = queue.popleft()  # 隊首元素出隊
  visited |= {url}  # 標記為已訪問
 
  print('已經(jīng)抓取: ' + str(cnt) + '   正在抓取 <---  ' + url)
  cnt += 1
  urlop = urllib.request.urlopen(url)
  if 'html' not in urlop.getheader('Content-Type'):
    continue
 
  # 避免程序異常中止, 用try..catch處理異常
  try:
    data = urlop.read().decode('utf-8')
  except:
    continue
 
  # 正則表達式提取頁面中所有隊列, 并判斷是否已經(jīng)訪問過, 然后加入待爬隊列
  linkre = re.compile('href="(.+?)"')
  for x in linkre.findall(data):
    if 'http' in x and x not in visited:
      queue.append(x)
      print('加入隊列 --->  ' + x)

上面的一個例子侥猬,是先爬取百度首頁例驹,獲取頁面帶有href的a標簽,然后放入隊列退唠,繼續(xù)爬取該地值得內(nèi)容狼荞,直到隊列里面沒有元素為止缩赛。
顯然這還不是我們想要的結果,咱們繼續(xù)來看

// content是爬取到的內(nèi)容
content = response.read().decode('utf-8')
pattern = re.compile('<div.*?class="author.*?>.*?<a.*?</a>.*?<a.*?>(.*?)</a>.*?<div.*?class'+
                     '="content".*?title="(.*?)">(.*?)</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
items = re.findall(pattern,content)  等同于  pattern.findall(content)
for item in items:
    print item[0],item[1],item[2],item[3],item[4]

1).? 是一個固定的搭配台汇,.和代表可以匹配任意無限多個字符,加上观蜗?表示使用非貪婪模式進行匹配,也就是我們會盡可能短地做匹配,以后我們還會大量用到 .*? 的搭配圆丹。

2)(.?)代表一個分組,在這個正則表達式中我們匹配了五個分組躯喇,在后面的遍歷item中运褪,item[0]就代表第一個(.?)所指代的內(nèi)容,item[1]就代表第二個(.*?)所指代的內(nèi)容玖瘸,以此類推秸讹。

其中,附加圖片的內(nèi)容我把圖片代碼整體摳了出來雅倒,這個對應item[3]璃诀,我們將忽略帶有圖片的分組。

import urllib
import urllib2
import re
 
page = 1
url = 'http://www.qiushibaike.com/hot/page/' + str(page)
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
    request = urllib2.Request(url,headers = headers)
    response = urllib2.urlopen(request)
    content = response.read().decode('utf-8')
    pattern = re.compile('<div.*?class="author.*?>.*?<a.*?</a>.*?<a.*?>(.*?)</a>.*?<div.*?class'+
                         '="content".*?title="(.*?)">(.*?)</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
    items = re.findall(pattern,content)
    for item in items:
        haveImg = re.search("img",item[3])
        if not haveImg:
            print item[0],item[1],item[2],item[4]
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

熟悉python的同學都知道蔑匣,它是一們面向對象的語言劣欢,我們下面就用面向對象的方式來完成這個抓取段子的功能。

__author__ = 'CQC'
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
import thread
import time

#糗事百科爬蟲類
class QSBK:

    #初始化方法裁良,定義一些變量
    def __init__(self):
        self.pageIndex = 1
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        #初始化headers
        self.headers = { 'User-Agent' : self.user_agent }
        #存放段子的變量凿将,每一個元素是每一頁的段子們
        self.stories = []
        #存放程序是否繼續(xù)運行的變量
        self.enable = False
    #傳入某一頁的索引獲得頁面代碼
    def getPage(self,pageIndex):
        try:
            url = 'http://www.qiushibaike.com/hot/page/' + str(pageIndex)
            #構建請求的request
            request = urllib2.Request(url,headers = self.headers)
            #利用urlopen獲取頁面代碼
            response = urllib2.urlopen(request)
            #將頁面轉化為UTF-8編碼
            pageCode = response.read().decode('utf-8')
            return pageCode

        except urllib2.URLError, e:
            if hasattr(e,"reason"):
                print u"連接糗事百科失敗,錯誤原因",e.reason
                return None


    #傳入某一頁代碼,返回本頁不帶圖片的段子列表
    def getPageItems(self,pageIndex):
        pageCode = self.getPage(pageIndex)
        if not pageCode:
            print "頁面加載失敗...."
            return None
        pattern = re.compile('<div.*?author">.*?<a.*?<img.*?>(.*?)</a>.*?<div.*?'+
                         'content">(.*?)<!--(.*?)-->.*?</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
        items = re.findall(pattern,pageCode)
        #用來存儲每頁的段子們
        pageStories = []
        #遍歷正則表達式匹配的信息
        for item in items:
            #是否含有圖片
            haveImg = re.search("img",item[3])
            #如果不含有圖片价脾,把它加入list中
            if not haveImg:
                replaceBR = re.compile('<br/>')
                text = re.sub(replaceBR,"\n",item[1])
                #item[0]是一個段子的發(fā)布者牧抵,item[1]是內(nèi)容,item[2]是發(fā)布時間,item[4]是點贊數(shù)
                pageStories.append([item[0].strip(),text.strip(),item[2].strip(),item[4].strip()])
        return pageStories

    #加載并提取頁面的內(nèi)容侨把,加入到列表中
    def loadPage(self):
        #如果當前未看的頁數(shù)少于2頁犀变,則加載新一頁
        if self.enable == True:
            if len(self.stories) < 2:
                #獲取新一頁
                pageStories = self.getPageItems(self.pageIndex)
                #將該頁的段子存放到全局list中
                if pageStories:
                    self.stories.append(pageStories)
                    #獲取完之后頁碼索引加一,表示下次讀取下一頁
                    self.pageIndex += 1
    
    #調用該方法秋柄,每次敲回車打印輸出一個段子
    def getOneStory(self,pageStories,page):
        #遍歷一頁的段子
        for story in pageStories:
            #等待用戶輸入
            input = raw_input()
            #每當輸入回車一次获枝,判斷一下是否要加載新頁面
            self.loadPage()
            #如果輸入Q則程序結束
            if input == "Q":
                self.enable = False
                return
            print u"第%d頁\t發(fā)布人:%s\t發(fā)布時間:%s\t贊:%s\n%s" %(page,story[0],story[2],story[3],story[1])
    
    #開始方法
    def start(self):
        print u"正在讀取糗事百科,按回車查看新段子,Q退出"
        #使變量為True骇笔,程序可以正常運行
        self.enable = True
        #先加載一頁內(nèi)容
        self.loadPage()
        #局部變量省店,控制當前讀到了第幾頁
        nowPage = 0
        while self.enable:
            if len(self.stories)>0:
                #從全局list中獲取一頁的段子
                pageStories = self.stories[0]
                #當前讀到的頁數(shù)加一
                nowPage += 1
                #將全局list中第一個元素刪除,因為已經(jīng)取出
                del self.stories[0]
                #輸出該頁的段子
                self.getOneStory(pageStories,nowPage)


spider = QSBK()
spider.start()

代碼我們是寫出來了笨触,但是這種用正則匹配獲取html內(nèi)容的方式實在是很復雜懦傍,那么我們怎么解決這個問題呢?既然我們能夠抓取網(wǎng)頁html元素旭旭,那么有沒有一種庫像jquery一樣能夠解析dom書呢谎脯,答案是有的,下面開始介紹Beautiful Soup持寄。

假設有這樣一段html代碼
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a  class="sister" id="link1">Elsie</a>,
<a  class="sister" id="link2">Lacie</a> and
<a  class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)
soup.title
# <title>The Dormouse's story</title> 

soup.title.name
# u'title'   相當于是獲得標簽名

soup.title.string
# u'The Dormouse's story'  獲取的是標簽里的文字內(nèi)容

soup.title.parent.name
# u'head'  熟悉jquery的同學一看就懂

soup.p
# <p class="title"><b>The Dormouse's story</b></p> 這種寫法其實獲得是第一個p元素

soup.p['class']
# u'title'  獲取第一個p的class屬性源梭,如果有多個class娱俺,返回list

soup.find_all('a')
[<a class="sister"  id="link1">Elsie</a>,
  <a class="sister"  id="link2">Lacie</a>,
  <a class="sister"  id="link3">Tillie</a>]
這里是查找html標簽里面所有的a元素,返回的是一個數(shù)組

soup.find(id="link3")
# <a class="sister"  id="link3">Tillie</a>
好吧废麻,這不是jquery里面的屬性選擇器嗎

soup.find(id="link3").get('href')
# http://example.com/tillie



相信大家看了上面之后荠卷,覺得還是很簡單的把,咱們接著玩下看烛愧。

html_doc = """
<html>
<head>
          <title>The Dormouse's story</title>
          <div>The Dormouse's story</div>
</head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a  class="sister" id="link1">Elsie</a>,
<a  class="sister" id="link2">Lacie</a> and
<a  class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)

head_tag = soup.head
head_tag
# <head><title>The Dormouse's story</title></head>

head_tag.contents
[<title>The Dormouse's story</title>,<div>The Dormouse's story</div>
]
獲取某個元素的子元素油宜,取某個可以以下標的方式

title_tag = soup.title
title_tag.parent
# <head>
          <title>The Dormouse's story</title>
          <div>The Dormouse's story</div>
   </head>

link = soup.a
link
# <a class="sister"  id="link1">Elsie</a>
for parent in link.parents:
    if parent is None:
        print(parent)
    else:
        print(parent.name)
# p
# body
# html
# [document]
# None
通過元素的 .parents 屬性可以遞歸得到元素的所有父輩節(jié)點,下面的例子使用了 .parents 方法遍歷了<a>標簽到根節(jié)點的所有節(jié)點.

講到兄弟節(jié)點可能和jquery不大一樣,分的特別細怜姿。
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
sibling_soup.b.next_sibling
# <c>text2</c>

sibling_soup.c.previous_sibling
# <b>text1</b>

當然我們也可通過 .next_siblings 和 .previous_siblings 屬性可以對當前節(jié)點的兄弟節(jié)點迭代輸出:
for sibling in soup.a.next_siblings:
    print(repr(sibling))
    # u',\n'
    # <a class="sister"  id="link2">Lacie</a>
    # u' and\n'
    # <a class="sister"  id="link3">Tillie</a>
    # u'; and they lived at the bottom of a well.'
    # None
需要注意的是兄弟節(jié)點也包括換行符和字符串等慎冤。

當然也可以使用正則來查找:
import re
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
# body
# b

soup.find_all("p", "title") 
# [<p class="title"><b>The Dormouse's story</b></p>]

其實還有什么append和insert方法,簡直和js dom操作如出一轍沧卢,jiang

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚁堤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子但狭,更是在濱河造成了極大的恐慌披诗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件立磁,死亡現(xiàn)場離奇詭異呈队,居然都是意外死亡,警方通過查閱死者的電腦和手機唱歧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門宪摧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人迈喉,你說我怎么就攤上這事绍刮。” “怎么了挨摸?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岁歉。 經(jīng)常有香客問我得运,道長,這世上最難降的妖魔是什么锅移? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任熔掺,我火速辦了婚禮,結果婚禮上非剃,老公的妹妹穿的比我還像新娘置逻。我一直安慰自己,他們只是感情好备绽,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布券坞。 她就那樣靜靜地躺著鬓催,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恨锚。 梳的紋絲不亂的頭發(fā)上宇驾,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音猴伶,去河邊找鬼课舍。 笑死,一個胖子當著我的面吹牛他挎,可吹牛的內(nèi)容都是我干的筝尾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼办桨,長吁一口氣:“原來是場噩夢啊……” “哼忿等!你這毒婦竟也來了?” 一聲冷哼從身側響起崔挖,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤贸街,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后狸相,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薛匪,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年脓鹃,在試婚紗的時候發(fā)現(xiàn)自己被綠了逸尖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘸右,死狀恐怖娇跟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情太颤,我是刑警寧澤苞俘,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站龄章,受9級特大地震影響吃谣,放射性物質發(fā)生泄漏。R本人自食惡果不足惜做裙,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一岗憋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锚贱,春花似錦仔戈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晋修。三九已至,卻和暖如春耐量,著一層夾襖步出監(jiān)牢的瞬間飞蚓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工廊蜒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趴拧,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓山叮,卻偏偏與公主長得像著榴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屁倔,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

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

  • 課程目標 爬取百度百科Python詞條相關多個頁面的詞條和簡述 并將數(shù)據(jù)以表格的形式保存成html 程序包含五個模...
    牽絲籠海閱讀 451評論 0 2
  • 這幾天發(fā)現(xiàn)一個比較適合Python爬蟲初學者的網(wǎng)站脑又,我是跟著里邊的kingname老師學習,這就當作是一個學習筆記...
    MuYi0420閱讀 510評論 0 4
  • 不知道該如何表達自己的心情锐借,文章會有點亂问麸,想到哪兒就寫到哪兒吧。 心里失落是真的钞翔,雖然已經(jīng)做好了不抱希望的打算严卖,但...
    輕訴離愁閱讀 326評論 0 0
  • 那一年的書聲朗朗仿佛還在耳邊,這一年模樣變了一圈布轿,又識了新友變老友.那一年的老友新友皆成舊哮笆,這一年再打聲招呼卻不...
    旿旿閱讀 288評論 0 1
  • 早上一睜眼稠肘,收到姍姍早上五點鐘發(fā)來的消息。她說: “妞萝毛,我剛下班到家项阴。每次通宵加班,都有種想死的感覺珊泳,只想趕緊逃回...
    白大俠閱讀 1,245評論 2 4