【Python】簡單的網(wǎng)絡(luò)爬蟲

完整代碼


# encoding:UTF-8

# from bs4 import BeautifulSoup

import urlparse

import urllib2

import re

import robotparser

import datetime

import time

import itertools

import Queue? # 同步的因篇、線程安全的隊列類

import lxml.html

import lxml.cssselect

import csv


def crawl_sitemap(url, scrape_callback=None):

? ? """

? ? 1.通過robots文件記錄的鏈接爬蟲

? ? :param url:

? ? :return:如果有回調(diào)函數(shù),返回回調(diào)結(jié)果

? ? """

? ? sitemap = urllib2.urlopen(url).read()

? ? links = re.findall('<loc>(.*?)</loc>', sitemap)

? ? if scrape_callback:

? ? ? ? for link in links:

? ? ? ? ? ? html = urllib2.urlopen(link).read()

? ? ? ? ? ? scrape_callback(link, html)


def crawl_id(url, scrape_callback=None):

? ? """

? ? 2.通過ID爬蟲,連續(xù)發(fā)生多次下載錯誤才會退出

? ? :param url:含有ID的鏈接的公共部分,沒有/

? ? :return:

? ? """

? ? max_error = 5

? ? num_error = 0

? ? throttle = Throttle(5)

? ? for page in itertools.count(1):? # 迭代器笔横,從1開始

? ? ? ? link = url + ("/-%d" % page)

? ? ? ? html = urllib2.urlopen(link).read()

? ? ? ? if html is None:

? ? ? ? ? ? num_error += 1

? ? ? ? ? ? if num_error == max_error:

? ? ? ? ? ? ? ? break

? ? ? ? else:? # 網(wǎng)頁存在

? ? ? ? ? ? throttle.wait(link)

? ? ? ? ? ? scrape_callback(link, html)

? ? ? ? ? ? num_error = 0


def link_crawler(seed_url, link_regex=None, delay=-1, max_depth=1, max_urls=-1,

? ? ? ? ? ? ? ? headers=None, user_agent="wswp", proxy=None, num_retries=2, scrape_callback=None):

? ? """

? ? 3.通過鏈接爬蟲,深度優(yōu)先竞滓,禁用某些功能可將其參數(shù)設(shè)為負數(shù)

? ? 待爬蟲隊列存在,逐一判斷robots訪問權(quán)限吹缔,在等待一定時間后進行下載商佑,

? ? 并根據(jù)訪問深度決定是否繼續(xù)進行訪問。如繼續(xù)厢塘,根據(jù)正則表達式匹配獲

? ? 取鏈接集茶没,逐一規(guī)范化后,若某鏈接沒有被訪問過晚碾,且域名和種子網(wǎng)址域名相同抓半,

? ? 則歸入待爬蟲隊列。每完成一次訪問格嘁,鏈接總數(shù)+1

? ? :param seed_url:種子鏈接

? ? :param link_regex:目標鏈接識別正則表達式

? ? :param user_agent:用戶代理

? ? :return:爬蟲結(jié)果

? ? """

? ? crawl_queue = Queue.deque([seed_url])

? ? seen = {seed_url: 0}

? ? num_urls = 0

? ? rp = get_robots(seed_url)

? ? throttle = Throttle(delay)

? ? headers = headers or {}

? ? if user_agent:

? ? ? ? headers['User-agent'] = user_agent

? ? while crawl_queue:

? ? ? ? url = crawl_queue.pop()

? ? ? ? depth = seen[url]

? ? ? ? if rp.can_fetch(user_agent, url):

? ? ? ? ? ? throttle.wait(url)

? ? ? ? ? ? html = download(url, headers, proxy=proxy, num_retries=num_retries)

? ? ? ? ? ? links = []

? ? ? ? ? ? if scrape_callback:

? ? ? ? ? ? ? ? # links.extend(scrape_callback(url, html) or [])

? ? ? ? ? ? ? ? scrape_callback(url, html)

? ? ? ? ? ? if depth != max_depth:

? ? ? ? ? ? ? ? # 獲取鏈接

? ? ? ? ? ? ? ? if link_regex:

? ? ? ? ? ? ? ? ? ? links.extend(link for link in get_links(html) if re.match(link_regex, link))

? ? ? ? ? ? ? ? # 如果沒有被訪問過笛求,且域名相同,歸入連接誒隊列

? ? ? ? ? ? ? ? for link in links:

? ? ? ? ? ? ? ? ? ? link = normalize(seed_url, link)

? ? ? ? ? ? ? ? ? ? if link not in seen:

? ? ? ? ? ? ? ? ? ? ? ? seen[link] = depth + 1

? ? ? ? ? ? ? ? ? ? ? ? if same_domain(seed_url, link):

? ? ? ? ? ? ? ? ? ? ? ? ? ? crawl_queue.append(link)

? ? ? ? ? ? num_urls += 1

? ? ? ? ? ? if num_urls == max_urls:

? ? ? ? ? ? ? ? break

? ? ? ? else:

? ? ? ? ? ? print("Blocked by robots.txt:", url)


class Throttle:

? ? """

? ? 在兩次下載之間添加時間延遲

? ? """

? ? def __init__(self, delay):

? ? ? ? self.delay = delay? # 延遲多長時間

? ? ? ? self.domains = {}? # 字典記錄域名最后一次被訪問的時間地圖

? ? def wait(self, url):

? ? ? ? """

? ? ? ? 功能:頁面休眠

? ? ? ? urlparse將url(http://開頭)解析成組件

? ? ? ? 組件:協(xié)議(scheme)糕簿、位置(netloc)探入、路徑(path)、可選參數(shù)(parameters)懂诗、查詢(query)蜂嗽、片段(fragment)

? ? ? ? :param url:

? ? ? ? :return:

? ? ? ? """

? ? ? ? domain = urlparse.urlparse(url).netloc

? ? ? ? last_accessed = self.domains.get(domain)

? ? ? ? if self.delay > 0 and last_accessed is not None:

? ? ? ? ? ? sleep_secs = self.delay - (datetime.datetime.now() - last_accessed).seconds

? ? ? ? ? ? if sleep_secs > 0:

? ? ? ? ? ? ? ? time.sleep(sleep_secs)

? ? ? ? self.domains[domain] = datetime.datetime.now()


class ScrapeCallback:

? ? def __init__(self):

? ? ? ? self.writer = csv.writer(open('countries.csv', 'w'))

? ? ? ? self.fields = ('area', 'population', 'iso', 'country', 'capital',

? ? ? ? ? ? ? ? ? ? ? 'continent', 'tld', 'currency_code', 'currency_name',

? ? ? ? ? ? ? ? ? ? ? 'phone', 'postal_code_format', 'postal_code_regex', 'languages')

? ? ? ? self.writer.writerow(self.fields)

? ? def __call__(self, url, html):

? ? ? ? """

? ? ? ? :param url:判斷是否是目標鏈接

? ? ? ? :param html:下載數(shù)據(jù)的頁面

? ? ? ? :return:

? ? ? ? """

? ? ? ? if re.search('/view/', url):

? ? ? ? ? ? tree = lxml.html.fromstring(html)

? ? ? ? ? ? row = []

? ? ? ? ? ? for field in self.fields:

? ? ? ? ? ? ? ? row.append(tree.cssselect('table>tr#places_{}__row>td.w2p_fw'.format(field))[0].text_content())

? ? ? ? ? ? self.writer.writerow(row)


def download(url, headers, proxy, num_retries, data=None):

? ? """

? ? 設(shè)置一般的請求后,根據(jù)爬蟲代理參數(shù)選擇是否使用特定處理器來獲取鏈接

? ? 若遇到50X網(wǎng)頁暫時無法訪問的情況响禽,嘗試多次后無果則退出

? ? :param url:鏈接

? ? :param user_agent:用戶代理

? ? :param proxy:協(xié)議徒爹,ip端口

? ? :param num_retries:出錯是嘗試訪問多少次

? ? :return: 整個網(wǎng)頁的源代碼

? ? """

? ? print("Downloading:", url)

? ? request = urllib2.Request(url, data, headers)

? ? opener = urllib2.build_opener()? # 用特定處理器來獲取urls

? ? if proxy:

? ? ? ? proxy_params = {urlparse.urlparse(url).scheme: proxy}

? ? ? ? opener.add_handler(urllib2.ProxyHandler(proxy_params))

? ? try:

? ? ? ? html = urllib2.urlopen(request).read()

? ? ? ? # # 數(shù)據(jù)獲取方式1:正則表達式(C、快芋类、使用困難隆嗅、靈活性差)

? ? ? ? # result = re.findall('<td class="w2p_fw">(.*?)</td>', html)

? ? ? ? # if result:

? ? ? ? #? ? print(result[1])

? ? ? ? # # 數(shù)據(jù)獲取方式2:通過beautifulsoup(Python、慢侯繁、安裝簡單)

? ? ? ? # soup = BeautifulSoup(html, 'html.parser')

? ? ? ? # tr = soup.find(attrs={'id': 'places_area__row'})

? ? ? ? # if tr:

? ? ? ? #? ? td = tr.find(attrs={'class': 'w2p_fw'})

? ? ? ? #? ? area = td.text

? ? ? ? #? ? print(area)

? ? ? ? # # 數(shù)據(jù)獲取方式3:通過lxml(快胖喳、大量數(shù)據(jù)抓取效果更明顯、安裝相對困難)

? ? ? ? # tree = lxml.html.fromstring(html)

? ? ? ? # td = tree.cssselect('tr#places_neighbours__row > td.w2p_fw')

? ? ? ? # if td:

? ? ? ? #? ? area = td[0].text_content()

? ? ? ? #? ? print(area)

? ? except urllib2.URLError as e:

? ? ? ? print("Download error:", e.reason)

? ? ? ? html = None

? ? ? ? if num_retries > 0:

? ? ? ? ? ? if hasattr(e, 'code') and 500 <= e.code <= 600:

? ? ? ? ? ? ? ? return download(url, headers, proxy, num_retries - 1, data)

? ? return html


def get_links(html):

? ? """

? ? 提取網(wǎng)頁中的所有鏈接

? ? :param html:

? ? :return:

? ? """

? ? webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']',

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? re.IGNORECASE)? # re.compile()函數(shù)將字符串形式的正則表達式轉(zhuǎn)換成模式

? ? return webpage_regex.findall(html)


def get_robots(url):

? ? """

? ? :param url:

? ? :return: 包含robots信息的對象

? ? """

? ? rp = robotparser.RobotFileParser()

? ? rp.set_url(urlparse.urljoin(url, '/robots.txt'))

? ? rp.read()

? ? return rp


def normalize(seed_url, link):

? ? """

? ? 鏈接規(guī)范化贮竟,相對路徑轉(zhuǎn)化成絕對路徑

? ? :param seed_link:

? ? :param link:

? ? :return:

? ? """

? ? link, _ = urlparse.urldefrag(link)? # 去掉碎部(鏈接#后的部分)

? ? return urlparse.urljoin(seed_url, link)


def same_domain(url1, url2):

? ? """

? ? 兩個鏈接的域名相同丽焊,為True

? ? :param url1:

? ? :param url2:

? ? :return:

? ? """

? ? return urlparse.urlparse(url1).netloc == urlparse.urlparse(url2).netloc


def main():

? ? ## 1.通過robots文件記錄的鏈接爬蟲

? ? # crawl_sitemap('http://example.webscraping.com/sitemap.xml', scrape_callback=ScrapeCallback())

? ? # # 2.通過ID爬蟲

? ? # crawl_id('http://example.webscraping.com/places/default/view',scrape_callback=ScrapeCallback())

? ? # 3.通過鏈接爬蟲

? ? link_crawler('http://example.webscraping.com', '/places/default/(view|index)',

? ? ? ? ? ? ? ? delay=0, num_retries=5, max_depth=2, user_agent='GoodCrawler', scrape_callback=ScrapeCallback())


main()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末较剃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子技健,更是在濱河造成了極大的恐慌写穴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雌贱,死亡現(xiàn)場離奇詭異啊送,居然都是意外死亡,警方通過查閱死者的電腦和手機欣孤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門馋没,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人降传,你說我怎么就攤上這事篷朵。” “怎么了婆排?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵声旺,是天一觀的道長。 經(jīng)常有香客問我泽论,道長艾少,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任翼悴,我火速辦了婚禮缚够,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鹦赎。我一直安慰自己谍椅,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布古话。 她就那樣靜靜地躺著雏吭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陪踩。 梳的紋絲不亂的頭發(fā)上杖们,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音肩狂,去河邊找鬼摘完。 笑死,一個胖子當著我的面吹牛傻谁,可吹牛的內(nèi)容都是我干的孝治。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谈飒!你這毒婦竟也來了岂座?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤杭措,失蹤者是張志新(化名)和其女友劉穎费什,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體手素,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡吕喘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刑桑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡募舟,死狀恐怖祠斧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拱礁,我是刑警寧澤琢锋,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站呢灶,受9級特大地震影響吴超,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸯乃,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鲸阻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缨睡,春花似錦鸟悴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陋守,卻和暖如春震贵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背水评。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工猩系, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人之碗。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓蝙眶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幽纷,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 一式塌、爬蟲的簡介爬蟲:一段自動抓取互聯(lián)網(wǎng)信息的程序。價值:互聯(lián)網(wǎng)數(shù)據(jù)友浸,為我所用峰尝。 二、簡單的爬蟲架構(gòu)爬蟲調(diào)度端——>...
    zzj丶閱讀 385評論 0 0
  • 書名:《用python寫網(wǎng)絡(luò)爬蟲》收恢,通過閱讀并記錄去學(xué)習(xí)武学,如果文章有什么錯誤的地方還希望指正本文參考了http:/...
    楓灬葉閱讀 2,873評論 2 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)伦意,斷路器火窒,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 在上一篇中 , 我們構(gòu)建了一個爬蟲驮肉, 可以通過跟蹤鏈接的方式下載我們所需的網(wǎng)頁熏矿。 但是爬蟲在下載網(wǎng)頁之后又將 結(jié)果...
    楓灬葉閱讀 1,953評論 0 5
  • 語法格式 說明: egrep 和 grep 的語法一致,區(qū)別在于元字符的使用上: 擴展的正則表達式與基本正則表達式...
    Michael_林閱讀 711評論 0 1