zoukankan      html  css  js  c++  java
  • 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"
    ", ""),
    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("
    "):
    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 交流群 分享资料,分享技术

  • 相关阅读:
    Win32串口API
    Windows核心编程 第4章 进程
    大家都来吐槽下12306的网站bug吧
    HttpRequest模拟Post和Get提交代码
    jquery.masonry + jquery.infinitescroll 实现瀑布流布局
    三层架构之泛型应用
    listView 中,大图标时,各个图标之间间距的控制
    Windows Phone APP的设计过程
    分享三个小故事
    最值得创业者聆听的10大TED演讲(中文字幕视频)
  • 原文地址:https://www.cnblogs.com/pypypy/p/12019283.html
Copyright © 2011-2022 走看看