python爬蟲實戰


用Python開發爬蟲是一件很輕松愉悅的事情,因為其相關庫較多,而且使用方便,短短十幾行代碼就可以完成一個爬蟲的開發;
但是,在應對具有反爬措施的網站,使用js動態加載的網站,App采集的時候就得動動腦子了;並且在開發分布式爬蟲,高性能爬蟲的時候更得用心設計。

Python開發爬蟲常用的工具總結

  1. reqeusts:Python HTTP網絡請求庫;
  2. pyquery: Python HTML DOM結構解析庫,采用類似JQuery的語法;
  3. BeautifulSoup:python HTML以及XML結構解析;
  4. selenium:Python自動化測試框架,可以用於爬蟲;
  5. phantomjs:無頭瀏覽器,可以配合selenium獲取js動態加載的內容;
  6. re:python內建正則表達式模塊;
  7. fiddler:抓包工具,原理就是是一個代理服務器,可以抓取手機包;
  8. anyproxy:代理服務器,可以自己撰寫rule截取request或者response,通常用於客戶端采集;
  9. celery:Python分布式計算框架,可用於開發分布式爬蟲;
  10. gevent:Python基於協程的網絡庫,可用於開發高性能爬蟲
  11. grequests:異步requests
  12. aiohttp:異步http client/server框架
  13. asyncio:python內建異步io,事件循環庫
  14. uvloop:一個非常快速的事件循環庫,配合asyncio效率極高
  15. concurrent:Python內建用於並發任務執行的擴展
  16. scrapy:python 爬蟲框架;
  17. Splash:一個JavaScript渲染服務,相當於一個輕量級的瀏覽器,配合lua腳本通過他的http API 解析頁面;
  18. Splinter:開源自動化Python web測試工具
  19. pyspider:Python爬蟲系統

網頁抓取思路

    1. 數據是否可以直接從HTML中獲取?數據直接嵌套在頁面的HTML結構中;
    2. 數據是否使用JS動態渲染到頁面中的?數據嵌套在js代碼中,然后采用js加載到頁面或者采用ajax渲染;
    3. 獲取的頁面使用是否需要認證?需要登錄后頁面才可以訪問;
    4. 數據是否直接可以通過API得到?有些數據是可以直接通過api獲取到,省去解析HTML的麻煩,大多數API都是以JSON格式返回數據;
    5. 來自客戶端的數據如何采集?例如:微信APP和微信客戶端

如何應對反爬

  1. 不要太過分,控制爬蟲的速率,別把人家整垮了,那就兩敗俱傷了;
  2. 使用代理隱藏真實IP,並且實現反爬;
  3. 讓爬蟲看起來像人類用戶,選擇性滴設置以下HTTP頭部:
    • Host:https://www.baidu.com
    • Connection:keep-alive
    • Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
    • UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36
    • Referer: http://s.weibo.com/user/gamelife1314&Refer=index
    • Accept-Encoding: gzip, deflate
    • Accept-Language: zh-CN,zh;q=0.8
  4. 查看網站的cookie,在某些情況下,請求需要添加cookie用於通過服務端的一些校驗;

案例說明

靜態頁面解析(獲取微信公眾號文章)
 1 import pyquery
 2 import re
 3 
 4 
 5 def weixin_article_html_parser(html):
 6     """
 7     解析微信文章,返回包含文章主體的字典信息
 8     :param html: 文章HTML源代碼
 9     :return:
10     """
11 
12     pq = pyquery.PyQuery(html)
13 
14     article = {
15         "weixin_id": pq.find("#js_profile_qrcode "
16                                ".profile_inner .profile_meta").eq(0).find("span").text().strip(),
17         "weixin_name": pq.find("#js_profile_qrcode .profile_inner strong").text().strip(),
18         "account_desc": pq.find("#js_profile_qrcode .profile_inner "
19                                 ".profile_meta").eq(1).find("span").text().strip(),
20         "article_title": pq.find("title").text().strip(),
21         "article_content": pq("#js_content").remove('script').text().replace(r"\r\n", ""),
22         "is_orig": 1 if pq("#copyright_logo").length > 0 else 0,
23         "article_source_url": pq("#js_sg_bar .meta_primary").attr('href') if pq(
24             "#js_sg_bar .meta_primary").length > 0 else '',
25 
26     }
27 
28     # 使用正則表達式匹配頁面中js腳本中的內容
29     match = {
30         "msg_cdn_url": {"regexp": "(?<=\").*(?=\")", "value": ""},  # 匹配文章封面圖
31         "var ct": {"regexp": "(?<=\")\d{10}(?=\")", "value": ""},  # 匹配文章發布時間
32         "publish_time": {"regexp": "(?<=\")\d{4}-\d{2}-\d{2}(?=\")", "value": ""},  # 匹配文章發布日期
33         "msg_desc": {"regexp": "(?<=\").*(?=\")", "value": ""},  # 匹配文章簡介
34         "msg_link": {"regexp": "(?<=\").*(?=\")", "value": ""},  # 匹配文章鏈接
35         "msg_source_url": {"regexp": "(?<=').*(?=')", "value": ""},  # 獲取原文鏈接
36         "var biz": {"regexp": "(?<=\")\w{1}.+?(?=\")", "value": ""},
37         "var idx": {"regexp": "(?<=\")\d{1}(?=\")", "value": ""},
38         "var mid": {"regexp": "(?<=\")\d{10,}(?=\")", "value": ""},
39         "var sn": {"regexp": "(?<=\")\w{1}.+?(?=\")", "value": ""},
40     }
41     count = 0
42     for line in html.split("\n"):
43         for item, value in match.items():
44             if item in line:
45                 m = re.search(value["regexp"], line)
46                 if m is not None:
47                     count += 1
48                     match[item]["value"] = m.group(0)
49                 break
50         if count >= len(match):
51             break
52 
53     article["article_short_desc"] = match["msg_desc"]["value"]
54     article["article_pos"] = int(match["var idx"]["value"])
55     article["article_post_time"] = int(match["var ct"]["value"])
56     article["article_post_date"] = match["publish_time"]["value"]
57     article["article_cover_img"] = match["msg_cdn_url"]["value"]
58     article["article_source_url"] = match["msg_source_url"]["value"]
59     article["article_url"] = "https://mp.weixin.qq.com/s?__biz={biz}&mid={mid}&idx={idx}&sn={sn}".format(
60         biz=match["var biz"]["value"],
61         mid=match["var mid"]["value"],
62         idx=match["var idx"]["value"],
63         sn=match["var sn"]["value"],
64     )
65 
66     return article
67 
68 
69 if __name__ == '__main__':
70 
71     from pprint import pprint
72     import requests
73     url = ("https://mp.weixin.qq.com/s?__biz=MzI1NjA0MDg2Mw==&mid=2650682990&idx=1"
74            "&sn=39419542de39a821bb5d1570ac50a313&scene=0#wechat_redirect")
75     pprint(weixin_article_html_parser(requests.get(url).text))
76 
77 # {'account_desc': '夜聽,讓更多的家庭越來越幸福。',
78 #  'article_content': '文字:安夢 \xa0 \xa0 聲音:劉筱 得到了什么?又失去了什么?',
79 #  'article_cover_img': 'http://mmbiz.qpic.cn/mmbiz_jpg/4iaBNpgEXstYhQEnbiaD0AwbKhmCVWSeCPBQKgvnSSj9usO4q997wzoicNzl52K1sYSDHBicFGL7WdrmeS0K8niaiaaA/0?wx_fmt=jpeg',
80 #  'article_pos': 1,
81 #  'article_post_date': '2017-07-02',
82 #  'article_post_time': 1499002202,
83 #  'article_short_desc': '周日    來自劉筱的晚安問候。',
84 #  'article_source_url': '',
85 #  'article_title': '【夜聽】走到這里',
86 #  'article_url': 'https://mp.weixin.qq.com/s?__biz=MzI1NjA0MDg2Mw==&mid=2650682990&idx=1&sn=39419542de39a821bb5d1570ac50a313',
87 #  'is_orig': 0,
88 #  'weixin_id': 'yetingfm',
89 #  'weixin_name': '夜聽'}
使用phantomjs解析js渲染的頁面–微博搜索

有些頁面采用復雜的js邏輯處理,包含各種Ajax請求,請求之間還包含一些加密操作,通過分析js邏輯重新渲染頁面拿到
想要的數據可謂比登天還難,沒有堅實的js基礎,不熟悉各種js框架,搞明白這種頁面就別想了;
采取類似瀏覽器的方式渲染頁面,直接獲取頁面HTML方便多了。

例如:http://s.weibo.com/ 搜索出來的結果是使用js動態渲染的,直接獲取HTML並不會得到搜索的結果,所以我們要運行
頁面中的js,將頁面渲染成功以后,再獲取它的HTML進行解析;

 

使用Python模擬登陸獲取cookie

有些網站比較蛋疼,通常需要登錄之后才可以獲取數據,下面展示一個簡單的例子:用於登錄網站嗎,獲取cookie,然后可以用於其他請求

但是,這里僅僅在沒有驗證碼的情況下,如果要有短信驗證,圖片驗證,郵箱驗證那就要另行設計了;

目標網站:http://www.newrank.cn,日期:2017-07-03,如果網站結構更改,就需要修改代以下碼了;

 1 #!/usr/bin/env python3
 2 # encoding: utf-8
 3 import time
 4 from urllib import parse
 5 
 6 from selenium import webdriver
 7 from selenium.common.exceptions import TimeoutException, WebDriverException
 8 from selenium.webdriver.common.action_chains import ActionChains
 9 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
10 from pyquery import PyQuery
11 
12 
13 def weibo_user_search(url: str):
14     """通過phantomjs獲取搜索的頁面html"""
15 
16     desired_capabilities = DesiredCapabilities.CHROME.copy()
17     desired_capabilities["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
18                                                                  "AppleWebKit/537.36 (KHTML, like Gecko) "
19                                                                  "Chrome/59.0.3071.104 Safari/537.36")
20     desired_capabilities["phantomjs.page.settings.loadImages"] = True
21     # 自定義頭部
22     desired_capabilities["phantomjs.page.customHeaders.Upgrade-Insecure-Requests"] = 1
23     desired_capabilities["phantomjs.page.customHeaders.Cache-Control"] = "max-age=0"
24     desired_capabilities["phantomjs.page.customHeaders.Connection"] = "keep-alive"
25 
26     driver = webdriver.PhantomJS(executable_path="/usr/bin/phantomjs",  # 設置phantomjs路徑
27                                  desired_capabilities=desired_capabilities,
28                                  service_log_path="ghostdriver.log",)
29     # 設置對象的超時時間
30     driver.implicitly_wait(1)
31     # 設置頁面完全加載的超時時間,包括頁面全部渲染,異步同步腳本都執行完成
32     driver.set_page_load_timeout(60)
33     # 設置異步腳本的超時時間
34     driver.set_script_timeout(60)
35 
36     driver.maximize_window()
37     try:
38         driver.get(url=url)
39         time.sleep(1)
40         try:
41             # 打開頁面之后做一些操作
42             company = driver.find_element_by_css_selector("p.company")
43             ActionChains(driver).move_to_element(company)
44         except WebDriverException:
45             pass
46         html = driver.page_source
47         pq = PyQuery(html)
48         person_lists = pq.find("div.list_person")
49         if person_lists.length > 0:
50             for index in range(person_lists.length):
51                 person_ele = person_lists.eq(index)
52                 print(person_ele.find(".person_name > a.W_texta").attr("title"))
53         return html
54     except (TimeoutException, Exception) as e:
55         print(e)
56     finally:
57         driver.quit()
58 
59 if __name__ == '__main__':
60     weibo_user_search(url="http://s.weibo.com/user/%s" % parse.quote("新聞"))
61 # 央視新聞
62 # 新浪新聞
63 # 新聞
64 # 新浪新聞客戶端
65 # 中國新聞周刊
66 # 中國新聞網
67 # 每日經濟新聞
68 # 澎湃新聞
69 # 網易新聞客戶端
70 # 鳳凰新聞客戶端
71 # 皇馬新聞
72 # 網絡新聞聯播
73 # CCTV5體育新聞
74 # 曼聯新聞
75 # 搜狐新聞客戶端
76 # 巴薩新聞
77 # 新聞日日睇
78 # 新垣結衣新聞社
79 # 看看新聞KNEWS
80 # 央視新聞評論
使用Python模擬登陸獲取cookie

有些網站比較蛋疼,通常需要登錄之后才可以獲取數據,下面展示一個簡單的例子:用於登錄網站嗎,獲取cookie,然后可以用於其他請求

但是,這里僅僅在沒有驗證碼的情況下,如果要有短信驗證,圖片驗證,郵箱驗證那就要另行設計了;

目標網站:http://www.newrank.cn,日期:2017-07-03,如果網站結構更改,就需要修改代以下碼了;

 1 #!/usr/bin/env python3
 2 # encoding: utf-8
 3 
 4 from time import sleep
 5 from pprint import pprint
 6 
 7 from selenium.common.exceptions import TimeoutException, WebDriverException
 8 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 9 from selenium import webdriver
10 
11 
12 def login_newrank():
13     """登錄新榜,獲取他的cookie信息"""
14 
15     desired_capabilities = DesiredCapabilities.CHROME.copy()
16     desired_capabilities["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
17                                                                  "AppleWebKit/537.36 (KHTML, like Gecko) "
18                                                                  "Chrome/59.0.3071.104 Safari/537.36")
19     desired_capabilities["phantomjs.page.settings.loadImages"] = True
20 
21     # 自定義頭部
22     desired_capabilities["phantomjs.page.customHeaders.Upgrade-Insecure-Requests"] = 1
23     desired_capabilities["phantomjs.page.customHeaders.Cache-Control"] = "max-age=0"
24     desired_capabilities["phantomjs.page.customHeaders.Connection"] = "keep-alive"
25 
26     # 填寫自己的賬戶進行測試
27     user = {
28         "mobile": "user",
29         "password": "password"
30     }
31 
32     print("login account: %s" % user["mobile"])
33 
34     driver = webdriver.PhantomJS(executable_path="/usr/bin/phantomjs",
35                                  desired_capabilities=desired_capabilities,
36                                  service_log_path="ghostdriver.log", )
37 
38     # 設置對象的超時時間
39     driver.implicitly_wait(1)
40     # 設置頁面完全加載的超時時間,包括頁面全部渲染,異步同步腳本都執行完成
41     driver.set_page_load_timeout(60)
42     # 設置異步腳本的超時時間
43     driver.set_script_timeout(60)
44 
45     driver.maximize_window()
46 
47     try:
48         driver.get(url="http://www.newrank.cn/public/login/login.html?back=http%3A//www.newrank.cn/")
49         driver.find_element_by_css_selector(".login-normal-tap:nth-of-type(2)").click()
50         sleep(0.2)
51         driver.find_element_by_id("account_input").send_keys(user["mobile"])
52         sleep(0.5)
53         driver.find_element_by_id("password_input").send_keys(user["password"])
54         sleep(0.5)
55         driver.find_element_by_id("pwd_confirm").click()
56         sleep(3)
57         cookies = {user["name"]: user["value"] for user in driver.get_cookies()}
58         pprint(cookies)
59 
60     except TimeoutException as exc:
61         print(exc)
62     except WebDriverException as exc:
63         print(exc)
64     finally:
65         driver.quit()
66 
67 if __name__ == '__main__':
68     login_newrank()
69 # login account: 15395100590
70 # {'CNZZDATA1253878005': '1487200824-1499071649-%7C1499071649',
71 #  'Hm_lpvt_a19fd7224d30e3c8a6558dcb38c4beed': '1499074715',
72 #  'Hm_lvt_a19fd7224d30e3c8a6558dcb38c4beed': '1499074685,1499074713',
73 #  'UM_distinctid': '15d07d0d4dd82b-054b56417-9383666-c0000-15d07d0d4deace',
74 #  'name': '15395100590',
75 #  'rmbuser': 'true',
76 #  'token': 'A7437A03346B47A9F768730BAC81C514',
77 #  'useLoginAccount': 'true'}

在獲取cookie之后就可以將獲得的cookie添加到后續的請求中了,但是因為cookie是具有有效期的,因此需要定時更新;
可以通過設計一個cookie池來實現,動態定時登錄一批賬號,獲取cookie之后存放在數據庫中(redis,MySQL等等),
請求的時候從數據庫中獲取一條可用cookie,並且添加在請求中訪問;

使用pyqt5爬個數據試試(PyQt 5.9.2)
import sys
import csv

import pyquery

from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView


class Browser(QWebEngineView):

    def __init__(self):
        super(Browser, self).__init__()
        self.__results = []
        self.loadFinished.connect(self.__result_available)

    @property
    def results(self):
        return self.__results

    def __result_available(self):
        self.page().toHtml(self.__parse_html)

    def __parse_html(self, html):
        pq = pyquery.PyQuery(html)
        for rows in [pq.find("#table_list tr"), pq.find("#more_list tr")]:
            for row in rows.items():
                columns = row.find("td")
                d = {
                    "avatar": columns.eq(1).find("img").attr("src"),
                    "url": columns.eq(1).find("a").attr("href"),
                    "name": columns.eq(1).find("a").attr("title"),
                    "fans_number": columns.eq(2).text(),
                    "view_num": columns.eq(3).text(),
                    "comment_num": columns.eq(4).text(),
                    "post_count": columns.eq(5).text(),
                    "newrank_index": columns.eq(6).text(),
                }
                self.__results.append(d)

        with open("results.csv", "a+", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=["name", "fans_number", "view_num", "comment_num", "post_count",
                                                   "newrank_index", "url", "avatar"])
            writer.writerows(self.results)

    def open(self, url: str):
        self.load(QUrl(url))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    browser = Browser()
    browser.open("https://www.newrank.cn/public/info/list.html?period=toutiao_day&type=data")
    browser.show()
    app.exec_()

887934385 交流群 分享資料,分享技術


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM