python爬虫_python爬虫入门 - CSDN
  • Python爬虫入门项目

    万次阅读 多人点赞 2019-09-21 00:02:43
    Python是什么 Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。 创始人Guido van Rossum是BBC出品英剧Monty Python’s Flying Circus(中文:蒙提·派森的...

    Python是什么

    Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。

    创始人Guido van Rossum是BBC出品英剧Monty Python’s Flying Circus(中文:蒙提·派森的飞行马戏团)的狂热粉丝,因而将自己创造的这门编程语言命名为Python。

    人生苦短,我用python,翻译自"Life is short, you need Python"

    Python英式发音:/ˈpaɪθən/ ,中文类似‘拍森’。而美式发音:/ˈpaɪθɑːn/,中文类似‘拍赏’。我看麻省理工授课教授读的是‘拍赏’,我觉得国内大多是读‘拍森’吧。

    2017年python排第一也无可争议,比较AI第一语言,在当下人工智能大数据大火的情况下,python无愧第一语言的称号,至于C、C++、java都是万年的老大哥了,在代码量比较方面,小编相信java肯定是完爆其它语言的。

    不过从这一年的编程语言流行趋势看,java依然是传播最多的,比较无论app、web、云计算都离不开,而其相对python而言,学习路径更困难一点,想要转行编程,而且追赶潮流,python已然是最佳语言。

    许多大型网站就是用Python开发的,国内:豆瓣、搜狐、金山、腾讯、盛大、网易、百度、阿里、淘宝、热酷、土豆、新浪、果壳…; 国外:谷歌、NASA、YouTube、Facebook、工业光魔、红帽…

    Python将被纳入高考内容

    浙江省信息技术课程改革方案已经出台,Python确定进入浙江省信息技术高考,从2018年起浙江省信息技术教材编程语言将会从vb更换为Python。其实不止浙江,教育大省北京和山东也确定要把Python编程基础纳入信息技术课程和高考的内容体系,Python语言课程化也将成为孩子学习的一种趋势。尤其山东省最新出版的小学信息技术六年级教材也加入了Python内容,小学生都开始接触Python语言了!!

    再不学习,又要被小学生完爆了。。。

     

    Python入门教程

    Python能做什么

    • 网络爬虫
    • Web应用开发
    • 系统网络运维
    • 科学与数字计算
    • 图形界面开发
    • 网络编程
    • 自然语言处理(NLP)
    • 人工智能
    • 区块链
    • 多不胜举。。。

    Python入门爬虫

    这是我的第一个python项目,在这里与大家分享出来~

    • 需求
      • 我们目前正在开发一款产品其功能大致是:用户收到短信如:购买了电影票或者火车票机票之类的事件。然后app读取短信,解析短信,获取时间地点,然后后台自动建立一个备忘录,在事件开始前1小时提醒用户。
    • 设计
      • 开始我们将解析的功能放在了服务端,但是后来考虑到用户隐私问题。后来将解析功能放到了app端,服务端只负责收集数据,然后将新数据发送给app端。
      • 关于服务端主要是分离出两个功能,一、响应app端请求返回数据。二、爬取数据,存入数据库。
      • 响应请求返回数据使用java来做,而爬取数据存入数据库使用python来做,这样分别使用不同语言来做是因为这两种语言各有优势,java效率比python高些,适合做web端,而爬取数据并不是太追求性能且python语言和大量的库适合做爬虫。
    • 代码
      • 本项目使用python3的版本
      • 获取源码:扫描下方关注微信公众号「裸睡的猪」回复:爬虫入门 获取
         

         

      • 了解这个项目你只需要有简单的python基础,能了解python语法就可以。其实我自己也是python没学完,然后就开始写,遇到问题就百度,边做边学这样才不至于很枯燥,因为python可以做一些很有意思的事情,比如模拟连续登录挣积分,比如我最近在写一个预定模范出行车子的python脚本。推荐看廖雪峰的python入门教程
      • 首先带大家看看我的目录结构,开始我打算是定义一个非常好非常全的规范,后来才发现由于自己不熟悉框架,而是刚入门级别,所以就放弃了。从简而入:
      • 下面咱们按照上图中的顺序,从上往下一个一个文件的讲解init.py包的标识文件,python包就是文件夹,当改文件夹下有一个init.py文件后它就成为一个package,我在这个包中引入一些py供其他py调用。

    init.py

    # -*- coding: UTF-8 -*-  
    
    # import need manager module  
    import MongoUtil  
    import FileUtil  
    import conf_dev  
    import conf_test  
    import scratch_airport_name  
    import scratch_flight_number  
    import scratch_movie_name  
    import scratch_train_number  
    import scratch_train_station  
    import MainUtil
    

    下面两个是配置文件,第一个是开发环境的(windows),第二个是测试环境的(linux),然后再根据不同系统启用不同的配置文件

    conf_dev.py

    # -*- coding: UTF-8 -*-  
    # the configuration file of develop environment  
    
    # path configure  
    data_root_path = 'E:/APK98_GNBJ_SMARTSERVER/Proj-gionee-data/smart/data'  
    
    # mongodb configure  
    user = "cmc"  
    pwd = "123456"  
    server = "localhost"  
    port = "27017"  
    db_name = "smartdb"
    

    conf_test.py

    # -*- coding: UTF-8 -*-  
    # the configuration file of test environment  
    
    #path configure  
    data_root_path = '/data/app/smart/data'  
    
    #mongodb configure  
    user = "smart"  
    pwd = "123456"  
    server = "10.8.0.30"  
    port = "27017"  
    db_name = "smartdb"
    

    下面文件是一个util文件,主要是读取原文件的内容,还有将新内容写入原文件。

    FileUtil.py

    # -*- coding: UTF-8 -*-  
    import conf_dev  
    import conf_test  
    import platform  
    
    
    # configure Multi-confronment  
    # 判断当前系统,并引入相对的配置文件
    platform_os = platform.system()  
    config = conf_dev  
    if (platform_os == 'Linux'):  
        config = conf_test  
    # path  
    data_root_path = config.data_root_path  
    
    
    # load old data  
    def read(resources_file_path, encode='utf-8'):  
        file_path = data_root_path + resources_file_path  
        outputs = []  
        for line in open(file_path, encoding=encode):  
            if not line.startswith("//"):  
                outputs.append(line.strip('\n').split(',')[-1])  
        return outputs  
    
    
    # append new data to file from scratch  
    def append(resources_file_path, data, encode='utf-8'):  
        file_path = data_root_path + resources_file_path  
        with open(file_path, 'a', encoding=encode) as f:  
            f.write(data)  
        f.close
    

    下面这个main方法控制着执行流程,其他的执行方法调用这个main方法

    MainUtil.py

    # -*- coding: UTF-8 -*-  
    
    import sys  
    from datetime import datetime  
    import MongoUtil  
    import FileUtil  
    
    # @param resources_file_path 资源文件的path  
    # @param base_url 爬取的连接  
    # @param scratch_func 爬取的方法  
    def main(resources_file_path, base_url, scratch_func):  
        old_data = FileUtil.read(resources_file_path)   #读取原资源  
        new_data = scratch_func(base_url, old_data)     #爬取新资源  
        if new_data:        #如果新数据不为空  
            date_new_data = "//" + datetime.now().strftime('%Y-%m-%d') + "\n" + "\n".join(new_data) + "\n"      #在新数据前面加上当前日期  
            FileUtil.append(resources_file_path, date_new_data)     #将新数据追加到文件中  
            MongoUtil.insert(resources_file_path, date_new_data)    #将新数据插入到mongodb数据库中  
        else:   #如果新数据为空,则打印日志  
            print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '----', getattr(scratch_func, '__name__'), ": nothing to update ")
    

    将更新的内容插入mongodb中

    MongoUtil.py

    # -*- coding: UTF-8 -*-  
    
    import platform  
    from pymongo import MongoClient  
    from datetime import datetime, timedelta, timezone  
    import conf_dev  
    import conf_test  
    
    # configure Multi-confronment  
    platform_os = platform.system()  
    config = conf_dev  
    if (platform_os == 'Linux'):  
        config = conf_test  
    # mongodb  
    uri = 'mongodb://' + config.user + ':' + config.pwd + '@' + config.server + ':' + config.port + '/' + config.db_name  
    
    
    # 将数据写入mongodb  
    # @author chenmc  
    # @param uri connect to mongodb  
    # @path save mongodb field  
    # @data save mongodb field  
    # @operation save mongodb field default value 'append'  
    # @date 2017/12/07 16:30  
    # 先在mongodb中插入一条自增数据 db.sequence.insert({ "_id" : "version","seq" : 1})  
    
    def insert(path, data, operation='append'):  
        client = MongoClient(uri)  
        resources = client.smartdb.resources  
        sequence = client.smartdb.sequence  
        seq = sequence.find_one({"_id": "version"})["seq"]      #获取自增id  
        sequence.update_one({"_id": "version"}, {"$inc": {"seq": 1}})       #自增id+1  
        post_data = {"_class": "com.gionee.smart.domain.entity.Resources", "version": seq, "path": path,  
                     "content": data, "status": "enable", "operation": operation,  
                     "createtime": datetime.now(timezone(timedelta(hours=8)))}  
        resources.insert(post_data)     #插入数据
    

    项目引入的第三方库,可使用pip install -r requirements.txt下载第三方库

    requirements.txt

    # need to install module# need to install module  
    bs4  
    pymongo  
    requests  
    json
    

    下面真正的执行方法来了,这五个py分别表示爬取五种信息:机场名、航班号、电影名、列车号、列车站。他们的结构都差不多,如下:

    第一部分:定义查找的url;
    第二部分:获取并与旧数据比较,返回新数据;
    第三部分:main方法,执行写入新数据到文件和mongodb中;
    

    scratch_airport_name.py:爬取全国机场

    # -*- coding: UTF-8 -*-  
    import requests  
    import bs4  
    import json  
    import MainUtil  
    
    resources_file_path = '/resources/airplane/airportNameList.ini'  
    scratch_url_old = 'https://data.variflight.com/profiles/profilesapi/search'  
    scratch_url = 'https://data.variflight.com/analytics/codeapi/initialList'  
    get_city_url = 'https://data.variflight.com/profiles/Airports/%s'  
    
    
    #传入查找网页的url和旧数据,然后本方法会比对原数据中是否有新的条目,如果有则不加入,如果没有则重新加入,最后返回新数据
    def scratch_airport_name(scratch_url, old_airports):  
        new_airports = []  
        data = requests.get(scratch_url).text  
        all_airport_json = json.loads(data)['data']  
        for airport_by_word in all_airport_json.values():  
            for airport in airport_by_word:  
                if airport['fn'] not in old_airports:  
                    get_city_uri = get_city_url % airport['id']  
                    data2 = requests.get(get_city_uri).text  
                    soup = bs4.BeautifulSoup(data2, "html.parser")  
                    city = soup.find('span', text="城市").next_sibling.text  
                    new_airports.append(city + ',' + airport['fn'])  
        return new_airports  
    
     #main方法,执行这个py,默认调用main方法,相当于java的main
    if __name__ == '__main__':  
        MainUtil.main(resources_file_path, scratch_url, scratch_airport_name)
    

    scratch_flight_number.py:爬取全国航班号

    #!/usr/bin/python  
    # -*- coding: UTF-8 -*-  
    
    import requests  
    import bs4  
    import MainUtil  
    
    resources_file_path = '/resources/airplane/flightNameList.ini'  
    scratch_url = 'http://www.variflight.com/sitemap.html?AE71649A58c77='  
    
    
    def scratch_flight_number(scratch_url, old_flights):  
        new_flights = []  
        data = requests.get(scratch_url).text  
        soup = bs4.BeautifulSoup(data, "html.parser")  
        a_flights = soup.find('div', class_='list').find_all('a', recursive=False)  
        for flight in a_flights:  
            if flight.text not in old_flights and flight.text != '国内航段列表':  
                new_flights.append(flight.text)  
        return new_flights  
    
    
    if __name__ == '__main__':  
        MainUtil.main(resources_file_path, scratch_url, scratch_flight_number)
    

    scratch_movie_name.py:爬取最近上映的电影

    #!/usr/bin/python  
    # -*- coding: UTF-8 -*-  
    import re  
    import requests  
    import bs4  
    import json  
    import MainUtil  
    
    # 相对路径,也是需要将此路径存入数据库  
    resources_file_path = '/resources/movie/cinemaNameList.ini'  
    scratch_url = 'http://theater.mtime.com/China_Beijing/'  
    
    
    # scratch data with define url  
    def scratch_latest_movies(scratch_url, old_movies):  
        data = requests.get(scratch_url).text  
        soup = bs4.BeautifulSoup(data, "html.parser")  
        new_movies = []  
        new_movies_json = json.loads(  
            soup.find('script', text=re.compile("var hotplaySvList")).text.split("=")[1].replace(";", ""))  
        coming_movies_data = soup.find_all('li', class_='i_wantmovie')  
        # 上映的电影  
        for movie in new_movies_json:  
            move_name = movie['Title']  
            if move_name not in old_movies:  
                new_movies.append(movie['Title'])  
        # 即将上映的电影  
        for coming_movie in coming_movies_data:  
            coming_movie_name = coming_movie.h3.a.text  
            if coming_movie_name not in old_movies and coming_movie_name not in new_movies:  
                new_movies.append(coming_movie_name)  
        return new_movies  
    
    
    if __name__ == '__main__':  
        MainUtil.main(resources_file_path, scratch_url, scratch_latest_movies)
    

    scratch_train_number.py:爬取全国列车号

    #!/usr/bin/python  
    # -*- coding: UTF-8 -*-  
    import requests  
    import bs4  
    import json  
    import MainUtil  
    
    resources_file_path = '/resources/train/trainNameList.ini'  
    scratch_url = 'http://www.59178.com/checi/'  
    
    
    def scratch_train_number(scratch_url, old_trains):  
        new_trains = []  
        resp = requests.get(scratch_url)  
        data = resp.text.encode(resp.encoding).decode('gb2312')  
        soup = bs4.BeautifulSoup(data, "html.parser")  
        a_trains = soup.find('table').find_all('a')  
        for train in a_trains:  
            if train.text not in old_trains and train.text:  
                new_trains.append(train.text)  
        return new_trains  
    
    
    if __name__ == '__main__':  
        MainUtil.main(resources_file_path, scratch_url, scratch_train_number)
    

    scratch_train_station.py:爬取全国列车站

    #!/usr/bin/python  
    # -*- coding: UTF-8 -*-  
    import requests  
    import bs4  
    import random  
    import MainUtil  
    
    resources_file_path = '/resources/train/trainStationNameList.ini'  
    scratch_url = 'http://www.smskb.com/train/'  
    
    
    def scratch_train_station(scratch_url, old_stations):  
        new_stations = []  
        provinces_eng = (  
            "Anhui", "Beijing", "Chongqing", "Fujian", "Gansu", "Guangdong", "Guangxi", "Guizhou", "Hainan", "Hebei",  
            "Heilongjiang", "Henan", "Hubei", "Hunan", "Jiangsu", "Jiangxi", "Jilin", "Liaoning", "Ningxia", "Qinghai",  
            "Shandong", "Shanghai", "Shanxi", "Shanxisheng", "Sichuan", "Tianjin", "Neimenggu", "Xianggang", "Xinjiang",  
            "Xizang",  
            "Yunnan", "Zhejiang")  
        provinces_chi = (  
            "安徽", "北京", "重庆", "福建", "甘肃", "广东", "广西", "贵州", "海南", "河北",  
            "黑龙江", "河南", "湖北", "湖南", "江苏", "江西", "吉林", "辽宁", "宁夏", "青海",  
            "山东", "上海", "陕西", "山西", "四川", "天津", "内蒙古", "香港", "新疆", "西藏",  
            "云南", "浙江")  
        for i in range(0, provinces_eng.__len__(), 1):  
            cur_url = scratch_url + provinces_eng[i] + ".htm"  
            resp = requests.get(cur_url)  
            data = resp.text.encode(resp.encoding).decode('gbk')  
            soup = bs4.BeautifulSoup(data, "html.parser")  
            a_stations = soup.find('left').find('table').find_all('a')  
            for station in a_stations:  
                if station.text not in old_stations:  
                    new_stations.append(provinces_chi[i] + ',' + station.text)  
        return new_stations  
    
    
    if __name__ == '__main__':  
        MainUtil.main(resources_file_path, scratch_url, scratch_train_station)
    

    将项目放到测试服务器(centos7系统)中运行起来,我写了一个crontab,定时调用他们,下面贴出crontab。

    /etc/crontab

    SHELL=/bin/bash  
    PATH=/sbin:/bin:/usr/sbin:/usr/bin  
    MAILTO=root  
    
    # For details see man 4 crontabs  
    
    # Example of job definition:  
    # .---------------- minute (0 - 59)  
    # |  .------------- hour (0 - 23)  
    # |  |  .---------- day of month (1 - 31)  
    # |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...  
    # |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat  
    # |  |  |  |  |  
    # *  *  *  *  * user-name  command to be executed  
      0  0  *  *  * root python3 /data/app/smart/py/scratch_movie_name.py    >> /data/logs/smartpy/out.log 2>&1  
      0  1  *  *  1 root python3 /data/app/smart/py/scratch_train_station.py >> /data/logs/smartpy/out.log 2>&1  
      0  2  *  *  2 root python3 /data/app/smart/py/scratch_train_number.py  >> /data/logs/smartpy/out.log 2>&1  
      0  3  *  *  4 root python3 /data/app/smart/py/scratch_flight_number.py >> /data/logs/smartpy/out.log 2>&1  
      0  4  *  *  5 root python3 /data/app/smart/py/scratch_airport_name.py  >> /data/logs/smartpy/out.log 2>&1
    

    后续

    目前项目已经正常运行了三个多月啦。。。

    有问题反馈

    在阅读与学习中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

    • 微信公众号:裸睡的猪
    • 在下面留言
    • 直接给我私信

    关于此公众号

    • 后期或提供各种软件的免费激活码
    • 推送python,java等编程技术文章和面试技巧
    • 当然你们可以将你们感兴趣的东西直接送给我
    • 谢谢你们真诚的关注,此公众号以后获得的收益将全部通过抽奖的形式送给大家
    • 以后如果博主要创业的话,也会在此公众号中挑选小伙伴哦~
    • 希望大家分享出去,让更多想学习python的朋友看到~

     

     

    展开全文
  • 我的第一个Python爬虫——谈心得

    万次阅读 多人点赞 2019-06-28 09:17:50
    相信各大高校应该都有本校APP或超级课程表之类的软件,在信息化的时代能快速收集/查询自己想要的咨询也是种很重要的能力,所以记下了这篇博客,用于总结我所学到的东西,以及用于记录我的第一个爬虫的初生。...

    2018年3月27日,继开学以来,开了软件工程和信息系统设计,想来想去也没什么好的题目,干脆就想弄一个实用点的,于是产生了做“学生服务系统”想法。相信各大高校应该都有本校APP或超级课程表之类的软件,在信息化的时代能快速收集/查询自己想要的咨询也是种很重要的能力,所以记下了这篇博客,用于总结我所学到的东西,以及用于记录我的第一个爬虫的初生。

    一、做爬虫所需要的基础

    要做一只爬虫,首先就得知道他会干些什么,是怎样工作的。所以得有一些关于HTML的前置知识,这一点做过网页的应该最清楚了。
       HTML(超文本标记语言),是一种标记性语言,本身就是一长串字符串,利用各种类似 < a >,< /a>这样的标签来识别内容,然后通过浏览器的实现标准来翻译成精彩的页面。当然,一个好看的网页并不仅仅只有HTML,毕竟字符串是静态的,只能实现静态效果,要作出漂亮的网页还需要能美化样式的CSS和实现动态效果的JavaScipt,只要是浏览器都是支持这些玩意儿的。
       嗯,我们做爬虫不需要了解太多,只需要了解HTML是基于文档对象模型(DOM)的,以树的结构,存储各种标记,就像这样:
       DOM的百度百科
       之后会用到这种思想来在一大堆HTML字符串中找出我们想要的东西。

    了解了这个然后还得了解网页和服务器之间是怎么通信的,这就得稍微了解点HTTP协议,基于TCP/IP的应用层协议,规定了浏览器和服务器之间的通信规则,简单粗暴的介绍几点和爬虫相关的就是:

    浏览器和服务器之间有如下几种通信方式:
       GET:向服务器请求资源,请求以明文的方式传输,一般就在URL上能看到请求的参数
       POST:从网页上提交表单,以报文的形式传输,请求资源
       还有几种比较少见就不介绍了。

    了解了这两点就可以准备工具了,当然,对爬虫有兴趣还可以了解一下爬虫的发展史。

    二、介绍几款优秀制作爬虫的辅助工具

    由于我是采用python3.6开发的,然后从上文的介绍中,也该知道了一只爬虫是需要从HTML中提取内容,以及需要和网页做交互等。
       如果不采用爬虫框架的话,我建议采用:
       
        BeautifulSoup 库 ,一款优秀的HTML/XML解析库,采用来做爬虫,
                  不用考虑编码,还有中日韩文的文档,其社区活跃度之高,可见一斑。
                  [] 这个在解析的时候需要一个解析器,在文档中可以看到,推荐lxml
                  
        Requests 库,一款比较好用的HTTP库,当然python自带有urllib以及urllib2等库,
                但用起来是绝对没有这款舒服的,哈哈
               
        Fiddler. 工具,这是一个HTTP抓包软件,能够截获所有的HTTP通讯。
              如果爬虫运行不了,可以从这里寻找答案,官方链接可能进不去,可以直接百度下载

    爬虫的辅助开发工具还有很多,比如Postman等,这里只用到了这三个,相信有了这些能减少不少开发阻碍。

    三、最简单的爬虫试例

    最简单的爬虫莫过于单线程的静态页面了,这甚至都不能叫爬虫,单单一句正则表达式即可匹配出所有内容,比如各种榜单:豆瓣电影排行榜,这类网站爬取规则变化比较少,用浏览器自带的F12的审查很容易找到需要爬取信息的特征:这里写图片描述
        见到花花绿绿的HTML代码不要害怕,一个一个点,直到找到需要的信息就行了,可以看到所有电影名都是在这样

    <div class = "pl2">
    

    之下的,每有一个这样的标签就代表一个电影,从他的孩子< span >中即可抓取到电影名。
    代码如下:

    from bs4 import BeautifulSoup
    from lxml import html
    import xml
    import requests
    
    url = "https://movie.douban.com/chart"
    f = requests.get(url)                 #Get该网页从而获取该html内容
    soup = BeautifulSoup(f.content, "lxml")  #用lxml解析器解析该网页的内容, 好像f.text也是返回的html
    #print(f.content.decode())								#尝试打印出网页内容,看是否获取成功
    #content = soup.find_all('div',class_="p12" )   #尝试获取节点,因为calss和关键字冲突,所以改名class_
    
    for k in soup.find_all('div',class_='pl2'):#,找到div并且class为pl2的标签
       a = k.find_all('span')       #在每个对应div标签下找span标签,会发现,一个a里面有四组span
       print(a[0].string)            #取第一组的span中的字符串
    

    抓取结果如下:
    这里写图片描述
        乍一看,就这么个玩意儿,这些电影名还不如直接自己去网页看,这有什么用呢?但是,你想想,只要你掌握了这种方法,如果有翻页你可以按照规则爬完了一页就解析另外一页HTML(通常翻页的时候URL会规律变化,也就是GET请求实现的翻页),也就是说,只要掌握的爬取方法,无论工作量有多么大都可以按你的心思去收集想要的数据了。

    四、需要模拟登录后再爬取的爬虫所需要的信息

    4.1.登录分析

    刚才的爬虫未免太简单,一般也不会涉及到反爬虫方面,这一次分析需要登录的页面信息的爬取,按照往例,首先打开一个网页:
        我选择了我学校信息服务的网站,登录地方的代码如下:
           这里写图片描述
        可以看到验证码都没有,就只有账号密码以及提交。光靠猜的当然是不行的,一般输入密码的地方都是POST请求。
        POST请求的响应流程就是 客户在网页上填上服务器准备好的表单并且提交,然后服务器处理表单做出回应。一般就是用户填写帐号、密码、验证码然后把这份表单提交给服务器,服务器从数据库进行验证,然后作出不同的反应。在这份POST表单中可能还有一些不需要用户填写的用脚本生成的隐藏属性作为反爬虫的手段。
        要知道表单格式可以先试着随便登录一次,然后在F12中的network中查看登录结果,如图:
        图1                                图1
        这里写图片描述                                 图2

    】如果用真正的账号密码登录,要记住勾选上面的Preserve log,这样即使网页发生了跳转之前的信息也还在。
    从上面的两张图中很容易发现其中的一个POST请求, login?serv…就是登录请求了
    可以看到这个登录请求所携带的信息有:
    General: 记录了请求方式,请求地址,以及服务器返回的状态号 200等
    Response Headers: 响应头,HTTP响应后传输的头部消息
    Request Headers: 请求头,重点!!,向服务器发送请求时,发出的头部消息,之中很多参数都是爬虫需要模拟出来传送给服务器的。
    From Data:表单,重点!!,在这里表单中有:

    username: 12345
    password: MTIzNDU=
    lt: e1s1
    _eventId: submit
    

    我明明都填的12345,为什么密码变了呢?可以看出这密码不是原始值,应该是编码后的产物,网站常用的几种编码/加密方法就几种,这里是采用的base64编码,如果对密码编码的方式没有头绪可以仔细看看登录前后页面的前端脚本。运气好可以看到encode函数什么的。

    4.2信息提取

    如果了解过Resquests库的文档就知道,发送一个一般的POST请求所需要的参数构造是这样的:

     r = requests.post(url,[data],[header],[json],[**kwargs])
     /*
    url -- URL for the new Request object.
    data -- (optional) Dictionary, bytes, or file-like object to send in the body of the Request.
    json -- (optional) json to send in the body of the Request.
    **kwargs -- Optional arguments that request takes.
    */
    

    从上面的两张图片中即可找到发送一个正确的请求所需要的参数,即 urldata
       url 即上面的 Request URL:
    Request URL: http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin
       data 即上面的From data:

    username: 12345
    password: MTIzNDU=
    lt: e1s1
    _eventId: submit
    

    收集到了必要的信息还得了解三点:
       一、登录后的网页和服务器建立了联系,所以能和服务器进行通信,但即使你从这个网页点击里面的超链接跳转到另外一个子网页,在新网页中还是保持登录状态的在不断的跳转中是怎么识别用户的呢?
       在这里,服务器端一般是采用的Cookie技术,登陆后给你一个Cookie,以后你发出跳转网页的请求就携带该Cookie,服务器就能知道是你在哪以什么状态点击的该页面,也就解决了HTTP传输的无状态问题。
       很明显,在模拟登录以后保持登录状态需要用得着这个Cookie,当然Cookie在请求头中是可见的,为了自己的账号安全,请不要轻易暴露/泄漏自己的Cookie

    二、先了解一下,用python程序访问网页的请求头的User-Agent是什么样的呢?没错,如下图所示,很容易分辨这是程序的访问,也就是服务器知道这个请求是爬虫访问的结果,如果服务器做了反爬虫措施程序就会访问失败,所以需要程序模拟浏览器头,让对方服务器认为你是使用某种浏览器去访问他们的。
       这里写图片描述
      
       三、查找表单隐藏参数的获取方式,在上文表单列表中有个lt参数,虽然我也不知道他是干嘛的,但通过POST传输过去的表单肯定是会经过服务器验证的,所以需要弄到这份参数,而这份参数一般都会在HTML页面中由JS脚本自动生成,可以由Beautifulsoup自动解析抓取。  
      
    关于Fiddler的使用和请求信息相关信息可以查看链接:https://zhuanlan.zhihu.com/p/21530833?refer=xmucpp
    嗯,最重要的几样东西已经收集完毕,对Cookie和请求头的作用也有了个大概的了解,然后开始发送请求试试吧~

    五、开始编码爬虫

    如果用urllib库发送请求,则需要自己编码Cookie这一块(虽然也只要几行代码),但用Requests库就不需要这样,在目前最新版本中,requests.Session提供了自己管理Cookie的持久性以及一系列配置,可以省事不少。
       先以面对过程的方式实验地去编码:

    from bs4 import BeautifulSoup
    from lxml import html
    import requests
    ####################################################################################
    #  在这先准备好请求头,需要爬的URL,表单参数生成函数,以及建立会话
    ############################# 1 #################################################
    header={
        "Accept": "text/html, application/xhtml+xml, image/jxr, */*",
        "Referer": "http://uia.hnist.cn/sso/login?service=http%3A%2F%2Fportal.hnist.\
    			    cn%2Fuser%2FsimpleSSOLogin",    
        "Accept-Language": "zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3",
        "Content-Type": "application/x-www-form-urlencoded",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "Keep-Alive",
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
     AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
        "Accept-Encoding": "gzip, deflate",
        "Origin": "http://uia.hnist.cn",
        "Upgrade-Insecure-Requests": "1",
       #Cookie由Session管理,这里不用传递过去,千万不要乱改头,我因为改了头的HOST坑了我两天
    }  
    
    School_login_url = 'http://uia.hnist.cn/sso/login? \
    service=http%3A%2F%2Fportal.hnist.cn%2Fuser%2FsimpleSSOLogin'#学校登录的URL
    
    page = requests.Session()     #用Session发出请求能自动处理Cookie等问题
    page.headers = header		 #为所有请求设置头
    page.get(School_login_url)    #Get该地址建立连接(通常GET该网址后,服务器会发送一些用于\
    					验证的参数用于识别用户,这些参数在这就全由requests.Session处理了)
    
    
    def Get_lt():	#获取参数 lt 的函数
        f = requests.get(School_login_url,headers = header)
        soup = BeautifulSoup(f.content, "lxml")  
        once = soup.find('input', {'name': 'lt'})['value']
        return once
    
    lt = Get_lt()  #获取lt
    
    From_Data = {   #表单
        'username': 'your username',
        'password': 'Base64 encoded password',   
        #之前说过密码是通过base64加密过的,这里得输入加密后的值,或者像lt一样写个函数
        'lt': lt,
        '_eventId': 'submit',
    }
    ############################# 1 end #############################
    
    ################################################################
    #  在这一段向登录网站发送POST请求,并判断是否成功返回正确的内容
    ############################# 2 #################################
    
    q = page.post(School_login_url,data=From_Data,headers=header) 
    #发送登陆请求
    
    #######查看POST请求状态##############
    #print(q.url)	#这句可以查看请求的URL
    #print(q.status_code)  #这句可以查看请求状态
    #for (i,j) in q.headers.items():
    #    print(i,':',j)		#这里可以查看响应头
    #print('\n\n')
    #for (i,j) in q.request.headers.items():
    #    print(i,':',j)		#这里可以查看请求头
    ####上面的内容用于判断爬取情况,也可以用fiddle抓包查看 ####
    
    f = page.get('http://uia.hnist.cn')	#GET需要登录后(携带cookie)才能查看的网站
    print("body:",f.text)
    
    ######## 进入查成绩网站,找到地址,请求并接收内容 #############
    
    proxies = {  #代理地址,这里代理被注释了,对后面没影响,这里也不需要使用代理....
    #"http": "http://x.x.x.x:x",
    #"https": "http://x.x.x.x:x",
    }
    
    ########  查成绩网站的text格式表单,其中我省略了很多...######
    str = """callCount=1
    httpSessionId=DA0080E0317A1AD0FDD3E09E095CB4B7.portal254
    scriptSessionId=4383521D7E8882CB2F7AB18F62EED380
    page=/web/guest/788
    """
    #### 这是由于该服务器关于表单提交部分设计比较垃圾,所以不用去在意表单内容含义 ###
    
    f = page.post('http://portal.hnist.cn/portal_bg_ext/dwr/plainjs/
    ShowTableAction.showContent.dwr',\data=str,proxies=proxies)
     #查成绩的地址,表单参数为上面的str
      
    ######  查看地址,返回状态,以及原始内容#######"""
    print("f:",f.url)
    print(f.status_code)
    text = f.content.decode('unicode_escape')
    print(text.encode().decode()) #因为原始内容中有\uxxx形式的编码,所以使用这句解码
    ###########################################"""
    ################################### 2 end #########################
    
    ###################################################################
    #  解析获得的内容,并清洗数据,格式化输出...
    ############################# 3 ####################################
    
    
    
    
    

    [] 如果使用了Fiddler,他会自动为Web的访问设置一个代理,这时候如果你关闭了Fiddler可能爬虫会无法正常工作,这时候你选择浏览器直连,或者设置爬虫的代理为Fiddler即可。
    [注2]爬虫不要频率太快,不要影响到别人服务器的正常运行,如果不小心IP被封了可以使用代理(重要数据不要使用不安全的代理),网上有很多收费/免费的代理,可以去试下。

    过程中获得的经验:

    • 在上面第一部分,不知道作用的参数不要乱填,只需要填几个最重要的就够了,比如UA,有时候填了不该填的请求将会返回错误状态.,尽量把可分离的逻辑写成函数来调用,比如生成的表单参数,加密方法等.
    • 在上面第二部分如果请求失败可以配合抓包软件查看程序和浏览器发送的请求有什么差别,遗漏了什么重要的地方,尽量让程序模仿浏览器的必要的行为。
    • 第三部分中,因为拿到的数据是如下图1这样的,所以需要最后输出后decode,然后再使用正则表达式提取出双引号中的内容连接诶成一个标记语言的形式,再使用Beautifulsoup解析获得需要的数据,如图2.
    • 中途可能利用的工具有:
      官方正则表达式学习网站
      HTML格式美化
      正则表达式测试

    图1
                                     图1     
    图2
                         图2

    六、爬虫技术的拓展与提高

      经历了困难重重,终于得到了想要的数据,对于异步请求,使用JS渲染页面后才展示数据的网页,又或是使用JS代码加密过的网页,如果花时间去分析JS代码来解密,简单的公有的加密方法倒是无所谓,但对于特别难的加密就有点费时费力了,在要保持抓取效率的情况下可以使用能使用Splash框架:
      这是一个Javascript渲染服务,它是一个实现了HTTP API的轻量级浏览器,Splash是用Python实现的,同时使用Twisted和QT。Twisted(QT)用来让服务具有异步处理能力,以发挥webkit的并发能力。
      就比如像上面返回成绩地址的表单参数,格式为text,并且无规律,有大几十行,如果要弄明白每个参数是什么意思,还不如加载浏览器的JS 或 使用浏览器自动化测试软件来获取HTML了,所以,遇到这种情况,在那么大一段字符串中,只能去猜哪些参数是必要的,哪些参数是不必要的,比如上面的,我就看出两个是有关于返回页面结果的,其余的有可能存在验证身份的,时间的什么的。

      对于信息的获取源,如果另外的网站也有同样的数据并且抓取难度更低,那么换个网站爬可能是个更好的办法,以及有的网站根据请求头中的UA会产生不同的布局和处理,比如用手机的UA可能爬取会更加简单。

    七、后记

      几天后我发现了另一个格式较好的页面,于是去爬那个网站,结果他是.jsp的,采用之前的方法跳转几个302之后就没有后续了…后来才猜想了解到,最后一个302可能是由JS脚本跳转的,而我没有执行JS脚本的环境,也不清楚他执行的哪个脚本,传入了什么参数,于是各种尝试和对比,最后发现:正常请求时,每次都多2个Cookie,开始我想,Cookie不是由Session管理不用去插手的吗?然后我想以正常方式获得该Cookie,请求了N个地址,结果始终得不到想要的Cookie,于是我直接使用Session.cookies.set('COMPANY_ID','10122')添加了两个Cookie,还真成了…神奇…
      当然,过了一段时间后,又不行了,于是仔细观察,发现每次就JSESSIONID这一个Cookie对结果有影响,传递不同的值到不同的页面还…虽然我不认同这种猜的,毫无逻辑效率的瞎试。但经历长时间的测试和猜测,对结果进行总结和整理也是能发现其中规律的。

      关于判断某动作是不是JS,可以在Internet选项中设置禁止使用JS

      关于失败了验证的方法,我强烈建议下载fiddler,利用新建视图,把登录过程中所有的图片,CSS等文件去掉以后放到新视图中,然后利用程序登录的过程也放一个视图当中,如果没有在响应中找到需要的Cookie,还可以在视图中方便的查看各个JS文件,比浏览器自带的F12好用太多了。 如下图:
    这里写图片描述

    总之,经过这段时间的尝试,我对爬虫也有了个初步的了解,在这方面,也有了自己做法:
      
    抓包请求 —> 模仿请求头和表单—>如果请求失败,则仔细对比正常访问和程序访问的数据包 —>成功则根据内容结构进行解析—>清清洗数据并展示

    展开全文
  • python爬虫【一】爬取文字

    万次阅读 多人点赞 2018-12-23 10:23:50
    我们在安装py是建议如果使用windows不要安装原生的py因为windows的c编译器原因会使某些套件安装起来有麻烦 ... py官网下载的是原生版本https://www.python.org/ ...爬虫的原理就是模仿人类登录不同的网页...

    我们在安装py是建议如果使用windows不要安装原生的py因为windows的c编译器原因会使某些套件安装起来有麻烦

    也就是安装anaconda版本的pyhttps://www.anaconda.com/download/#windows

    py官网下载的是原生版本https://www.python.org/

    本文爬取文字使用原生python进行演示

    爬虫的原理就是模仿人类登录不同的网页 然后使用开发者工具来获得服务器传回的信息 将其按照特定格式储存起来

    简单说就是ETL(数据获取extract,数据转换translation,数据保存loading)

     

    如果文中的代码直接复制进py的编辑器会打乱  呢就先copy到notepad++  然后在copy到编辑器就ok了

     

    我们以chrome浏览器为示例

    首先我们打开一个页面右键检查 或者使用f12

    点击刷新/或按下f5我们可以看到服务器返回的内容 

     

    我们可以看到 我们需要的内容

     

    这里可以看到一个get我们要注意这里有了这些经验我们就可以开始利用无所不能的python来完成这些过程 

    我们在安装过程中一定要注意是在cmd下安装不能再python环境下安装!!! 

    py环境会失败

    打开cmd输入

    pip install requests
    pip install beautifulsoup4

    同理安装 anaconda 的库

    然后安装jypyter

    下面安装完成后只需要输入

    会自动打开浏览器 我们通过一个port访问自己电脑 然后可以创建python的项目

    下面我们开始写一个程序模拟人类点击的过程

    但是因为我们爬取文字等拿原生py就可以做到我们就不使用anaconda作为案例了

    下面我们打开notepad++这个文本编辑器有中文版并且免费

    https://notepad-plus-plus.org/

    或者直接使用py的idle交互式编译器

    在这里直接复制网址 

    我们运行就可以获取获取网页html的所有内容

     这样我们的第一只爬虫就写好了,但是怎样获取到我们需要的信息呢?

    import requests
    res = requests.get('http://www.jinyongwang.com/shen/781.html')
    res.encoding = 'utf-8'
    print(res.text)

    点击f5运行我们会发现程序会卡死这是因为我们打开的文字太多了

    所以我们需要将爬取的文字放入二进制文件

    并且删除标签 选择正确的内容

    现在我们强行关闭shell

    加入代码可以爬出前500行的代码

    import requests
    url='http://www.jinyongwang.com/shen/781.html'
    r = requests.get(url,timeout=30)
    type(r) 		#查看类型
    print(r.text[:500])	#显示前500字节内容
    print(r.status_code)    #显示状态码

     

    下面我们使用beautifulsoup

    先来看html的结构

    bs4套件的使用模板

    import requests
    from bs4 import BeautifulSoup
    url = 'https://python123.io/ws/demo.html' 
    r =  requests.get(url,timeout=30)
    r.encoding = r.apparent_encoding      
    #分析页面内容,以中文编码显示
    html = r.text
    soup = BeautifulSoup(html, 'html.parser') 
    
    #访问head、body、a、p、b
    soup.find_all(‘a’)、soup.body.contents、soup.prettify()
    

    使用beautifulsoup寻找标签

    url = http://news.qq.com/a/20170504/012032.htm
    div.hd	L131
    div.a_info	L135
    P.text	L164
    
    
    title = soup.select("div.hd > h1")
    time = soup.select("div.a_Info > span.a_time")
    paras = soup.select("div.Cnt-Main-Article-QQ > p.text")

    我们注意py中是不需要声明变量的

    寻找特定的HTML标签

    找到h1标签的文本

    soup = BeautifulSoup(html_sample)
    
    header = soup.select('h1')  #h1可以改成任意标签
    
    print(header)
    
    print(header[0])            #去除中括号保留标签和文本
    
    print(header[0].text)       #去除标签保留文本

    寻找特定css元素

    id类

    id名前加#

     alink = soup('#title')
    
    print(laink)

    class类

    class名前加.

    soup = BeautifulSoup(html_sample)
    
    for link in soup.select('.link'):
    
        print(link)

    取得标签中的属性

    取得所有a标签的链接

    a tag找出所有herf(超链接)的链接

    a = '<a herf="#" qwe=123 asd=456>i am a link</a>'
    soup = BeautifulSoup(a,'html.parser')
    print(soup.select('a')[0]['href'])
    print(soup.select('a')[0]['qwe'])

     

    保存文本文件

    fo = open("text.txt", "w+")
        fo.writelines(title[0].get_text() + "\n")
        fo.writelines(time[0].get_text() + "\n")
        for para in paras:
            if len(para) > 0:
                fo.writelines(para.get_text() + "\n\n")
        fo.writelines(author[0].get_text() + '\n')
        fo.close()

    于是我们将代码结合写出一个爬虫将小说写入二进制文件中

    import re
    import requests
    from bs4 import BeautifulSoup
    def getHTML(url):
        try:
            r = requests.get(url,timeout=30)
            r.raise_for_status()
            r.encoding = r.apparent_encoding
            return r.text
        except:
            return ""
    
    def getContent(url):
        html = getHTML(url)
        soup = BeautifulSoup(html,'html.parser')
        title = soup.select('div.mbtitle')
        paras_tmp = soup.select('p')
        paras = paras_tmp[3:]
        return paras
    
    def saveFile(text):
        f=open('novel.txt','w')
        for t in text:
            if len(t) > 0:
                f.writelines(t.get_text() + "\n\n")
        f.close()
        
    def main():
        url = 'http://www.jinyongwang.com/shen/781.html'
        text = getContent(url)
        saveFile(text)
    
    main()

    运行完出现我们需要的文档

    打开就是我们需要的文本了

     

    展开全文
  • Python爬虫的N种姿势

    万次阅读 多人点赞 2019-06-17 09:52:54
      前几天,在微信公众号(Python爬虫及算法)上有个人问了笔者一个问题,如何利用爬虫来实现如下的需求,需要爬取的网页如下(网址为:https://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&...

    问题的由来

      前几天,在微信公众号(Python爬虫及算法)上有个人问了笔者一个问题,如何利用爬虫来实现如下的需求,需要爬取的网页如下(网址为:https://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0):

      我们的需求为爬取红色框框内的名人(有500条记录,图片只展示了一部分)的 名字以及其介绍,关于其介绍,点击该名人的名字即可,如下图:

    这就意味着我们需要爬取500个这样的页面,即500个HTTP请求(暂且这么认为吧),然后需要提取这些网页中的名字和描述,当然有些不是名人,也没有描述,我们可以跳过。最后,这些网页的网址在第一页中的名人后面可以找到,如George Washington的网页后缀为Q23.
      爬虫的需求大概就是这样。

    爬虫的4种姿势

      首先,分析来爬虫的思路:先在第一个网页(https://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0)中得到500个名人所在的网址,接下来就爬取这500个网页中的名人的名字及描述,如无描述,则跳过。
      接下来,我们将介绍实现这个爬虫的4种方法,并分析它们各自的优缺点,希望能让读者对爬虫有更多的体会。实现爬虫的方法为:

    • 一般方法(同步,requests+BeautifulSoup)
    • 并发(使用concurrent.futures模块以及requests+BeautifulSoup)
    • 异步(使用aiohttp+asyncio+requests+BeautifulSoup)
    • 使用框架Scrapy

    一般方法

      一般方法即为同步方法,主要使用requests+BeautifulSoup,按顺序执行。完整的Python代码如下:

    import requests
    from bs4 import BeautifulSoup
    import time
    
    # 开始时间
    t1 = time.time()
    print('#' * 50)
    
    url = "http://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0"
    # 请求头部
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    # 发送HTTP请求
    req = requests.get(url, headers=headers)
    # 解析网页
    soup = BeautifulSoup(req.text, "lxml")
    # 找到name和Description所在的记录
    human_list = soup.find(id='mw-whatlinkshere-list')('li')
    
    urls = []
    # 获取网址
    for human in human_list:
        url = human.find('a')['href']
        urls.append('https://www.wikidata.org'+url)
    
    # 获取每个网页的name和description
    def parser(url):
        req = requests.get(url)
        # 利用BeautifulSoup将获取到的文本解析成HTML
        soup = BeautifulSoup(req.text, "lxml")
        # 获取name和description
        name = soup.find('span', class_="wikibase-title-label")
        desc = soup.find('span', class_="wikibase-descriptionview-text")
        if name is not None and desc is not None:
            print('%-40s,\t%s'%(name.text, desc.text))
    
    for url in urls:
        parser(url)
    
    t2 = time.time() # 结束时间
    print('一般方法,总共耗时:%s' % (t2 - t1))
    print('#' * 50)
    

    输出的结果如下(省略中间的输出,以…代替):

    ##################################################
    George Washington                       ,	first President of the United States
    Douglas Adams                           ,	British author and humorist (1952–2001)
    ......
    Willoughby Newton                       ,	Politician from Virginia, USA
    Mack Wilberg                            ,	American conductor
    一般方法,总共耗时:724.9654655456543
    ##################################################
    

    使用同步方法,总耗时约725秒,即12分钟多。
      一般方法虽然思路简单,容易实现,但效率不高,耗时长。那么,使用并发试试看。

    并发方法

      并发方法使用多线程来加速一般方法,我们使用的并发模块为concurrent.futures模块,设置多线程的个数为20个(实际不一定能达到,视计算机而定)。完整的Python代码如下:

    import requests
    from bs4 import BeautifulSoup
    import time
    from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
    
    # 开始时间
    t1 = time.time()
    print('#' * 50)
    
    url = "http://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0"
    # 请求头部
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    # 发送HTTP请求
    req = requests.get(url, headers=headers)
    # 解析网页
    soup = BeautifulSoup(req.text, "lxml")
    # 找到name和Description所在的记录
    human_list = soup.find(id='mw-whatlinkshere-list')('li')
    
    urls = []
    # 获取网址
    for human in human_list:
        url = human.find('a')['href']
        urls.append('https://www.wikidata.org'+url)
    
    # 获取每个网页的name和description
    def parser(url):
        req = requests.get(url)
        # 利用BeautifulSoup将获取到的文本解析成HTML
        soup = BeautifulSoup(req.text, "lxml")
        # 获取name和description
        name = soup.find('span', class_="wikibase-title-label")
        desc = soup.find('span', class_="wikibase-descriptionview-text")
        if name is not None and desc is not None:
            print('%-40s,\t%s'%(name.text, desc.text))
    
    # 利用并发加速爬取
    executor = ThreadPoolExecutor(max_workers=20)
    # submit()的参数: 第一个为函数, 之后为该函数的传入参数,允许有多个
    future_tasks = [executor.submit(parser, url) for url in urls]
    # 等待所有的线程完成,才进入后续的执行
    wait(future_tasks, return_when=ALL_COMPLETED)
    
    t2 = time.time() # 结束时间
    print('并发方法,总共耗时:%s' % (t2 - t1))
    print('#' * 50)
    

    输出的结果如下(省略中间的输出,以…代替):

    ##################################################
    Larry Sanger                            ,	American former professor, co-founder of Wikipedia, founder of Citizendium and other projects
    Ken Jennings                            ,	American game show contestant and writer
    ......
    Antoine de Saint-Exupery                ,	French writer and aviator
    Michael Jackson                         ,	American singer, songwriter and dancer
    并发方法,总共耗时:226.7499692440033
    ##################################################
    

    使用多线程并发后的爬虫执行时间约为227秒,大概是一般方法的三分之一的时间,速度有了明显的提升啊!多线程在速度上有明显提升,但执行的网页顺序是无序的,在线程的切换上开销也比较大,线程越多,开销越大。
      关于多线程与一般方法在速度上的比较,可以参考文章:Python爬虫之多线程下载豆瓣Top250电影图片

    异步方法

      异步方法在爬虫中是有效的速度提升手段,使用aiohttp可以异步地处理HTTP请求,使用asyncio可以实现异步IO,需要注意的是,aiohttp只支持3.5.3以后的Python版本。使用异步方法实现该爬虫的完整Python代码如下:

    import requests
    from bs4 import BeautifulSoup
    import time
    import aiohttp
    import asyncio
    
    # 开始时间
    t1 = time.time()
    print('#' * 50)
    
    url = "http://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0"
    # 请求头部
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    # 发送HTTP请求
    req = requests.get(url, headers=headers)
    # 解析网页
    soup = BeautifulSoup(req.text, "lxml")
    # 找到name和Description所在的记录
    human_list = soup.find(id='mw-whatlinkshere-list')('li')
    
    urls = []
    # 获取网址
    for human in human_list:
        url = human.find('a')['href']
        urls.append('https://www.wikidata.org'+url)
    
    # 异步HTTP请求
    async def fetch(session, url):
        async with session.get(url) as response:
            return await response.text()
            
    # 解析网页
    async def parser(html):
        # 利用BeautifulSoup将获取到的文本解析成HTML
        soup = BeautifulSoup(html, "lxml")
        # 获取name和description
        name = soup.find('span', class_="wikibase-title-label")
        desc = soup.find('span', class_="wikibase-descriptionview-text")
        if name is not None and desc is not None:
            print('%-40s,\t%s'%(name.text, desc.text))
    
    # 处理网页,获取name和description
    async def download(url):
        async with aiohttp.ClientSession() as session:
            try:
                html = await fetch(session, url)
                await parser(html)
            except Exception as err:
                print(err)
    
    # 利用asyncio模块进行异步IO处理
    loop = asyncio.get_event_loop()
    tasks = [asyncio.ensure_future(download(url)) for url in urls]
    tasks = asyncio.gather(*tasks)
    loop.run_until_complete(tasks)
    
    t2 = time.time() # 结束时间
    print('使用异步,总共耗时:%s' % (t2 - t1))
    print('#' * 50)
    

    输出结果如下(省略中间的输出,以…代替):

    ##################################################
    Frédéric Taddeï                         ,	French journalist and TV host
    Gabriel Gonzáles Videla                 ,	Chilean politician
    ......
    Denmark                                 ,	sovereign state and Scandinavian country in northern Europe
    Usain Bolt                              ,	Jamaican sprinter and soccer player
    使用异步,总共耗时:126.9002583026886
    ##################################################
    

    显然,异步方法使用了异步和并发两种提速方法,自然在速度有明显提升,大约为一般方法的六分之一。异步方法虽然效率高,但需要掌握异步编程,这需要学习一段时间。
      关于异步方法与一般方法在速度上的比较,可以参考文章:利用aiohttp实现异步爬虫
      如果有人觉得127秒的爬虫速度还是慢,可以尝试一下异步代码(与之前的异步代码的区别在于:仅仅使用了正则表达式代替BeautifulSoup来解析网页,以提取网页中的内容):

    import requests
    from bs4 import BeautifulSoup
    import time
    import aiohttp
    import asyncio
    import re
    
    # 开始时间
    t1 = time.time()
    print('#' * 50)
    
    url = "http://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0"
    # 请求头部
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    # 发送HTTP请求
    req = requests.get(url, headers=headers)
    # 解析网页
    soup = BeautifulSoup(req.text, "lxml")
    # 找到name和Description所在的记录
    human_list = soup.find(id='mw-whatlinkshere-list')('li')
    
    urls = []
    # 获取网址
    for human in human_list:
        url = human.find('a')['href']
        urls.append('https://www.wikidata.org' + url)
    
    # 异步HTTP请求
    async def fetch(session, url):
        async with session.get(url) as response:
            return await response.text()
    
    # 解析网页
    async def parser(html):
        # 利用正则表达式解析网页
        try:
            name = re.findall(r'<span class="wikibase-title-label">(.+?)</span>', html)[0]
            desc = re.findall(r'<span class="wikibase-descriptionview-text">(.+?)</span>', html)[0]
            print('%-40s,\t%s' % (name, desc))
        except Exception as err:
            pass
    
    # 处理网页,获取name和description
    async def download(url):
        async with aiohttp.ClientSession() as session:
            try:
                html = await fetch(session, url)
                await parser(html)
            except Exception as err:
                print(err)
    
    # 利用asyncio模块进行异步IO处理
    loop = asyncio.get_event_loop()
    tasks = [asyncio.ensure_future(download(url)) for url in urls]
    tasks = asyncio.gather(*tasks)
    loop.run_until_complete(tasks)
    
    t2 = time.time()  # 结束时间
    print('使用异步(正则表达式),总共耗时:%s' % (t2 - t1))
    print('#' * 50)
    

    输出的结果如下(省略中间的输出,以…代替):

    ##################################################
    Dejen Gebremeskel                       ,	Ethiopian long-distance runner
    Erik Kynard                             ,	American high jumper
    ......
    Buzz Aldrin                             ,	American astronaut
    Egon Krenz                              ,	former General Secretary of the Socialist Unity Party of East Germany
    使用异步(正则表达式),总共耗时:16.521944999694824
    ##################################################
    

    16.5秒,仅仅为一般方法的43分之一,速度如此之快,令人咋舌(感谢某人提供的尝试)。笔者虽然自己实现了异步方法,但用的是BeautifulSoup来解析网页,耗时127秒,没想到使用正则表达式就取得了如此惊人的效果。可见,BeautifulSoup解析网页虽然快,但在异步方法中,还是限制了速度。但这种方法的缺点为,当你需要爬取的内容比较复杂时,一般的正则表达式就难以胜任了,需要另想办法。

    爬虫框架Scrapy

      最后,我们使用著名的Python爬虫框架Scrapy来解决这个爬虫。我们创建的爬虫项目为wikiDataScrapy,项目结构如下:

    在settings.py中设置“ROBOTSTXT_OBEY = False”. 修改items.py,代码如下:

    # -*- coding: utf-8 -*-
    
    import scrapy
    
    class WikidatascrapyItem(scrapy.Item):
        # define the fields for your item here like:
        name = scrapy.Field()
        desc = scrapy.Field()
    

    然后,在spiders文件夹下新建wikiSpider.py,代码如下:

    import scrapy.cmdline
    from wikiDataScrapy.items import WikidatascrapyItem
    import requests
    from bs4 import BeautifulSoup
    
    # 获取请求的500个网址,用requests+BeautifulSoup搞定
    def get_urls():
        url = "http://www.wikidata.org/w/index.php?title=Special:WhatLinksHere/Q5&limit=500&from=0"
        # 请求头部
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
        # 发送HTTP请求
        req = requests.get(url, headers=headers)
        # 解析网页
        soup = BeautifulSoup(req.text, "lxml")
        # 找到name和Description所在的记录
        human_list = soup.find(id='mw-whatlinkshere-list')('li')
    
        urls = []
        # 获取网址
        for human in human_list:
            url = human.find('a')['href']
            urls.append('https://www.wikidata.org' + url)
    
        # print(urls)
        return urls
    
    # 使用scrapy框架爬取
    class bookSpider(scrapy.Spider):
        name = 'wikiScrapy'  # 爬虫名称
        start_urls = get_urls()  # 需要爬取的500个网址
    
        def parse(self, response):
            item = WikidatascrapyItem()
            # name and description
            item['name'] = response.css('span.wikibase-title-label').xpath('text()').extract_first()
            item['desc'] = response.css('span.wikibase-descriptionview-text').xpath('text()').extract_first()
    
            yield item
    
    # 执行该爬虫,并转化为csv文件
    scrapy.cmdline.execute(['scrapy', 'crawl', 'wikiScrapy', '-o', 'wiki.csv', '-t', 'csv'])
    

    输出结果如下(只包含最后的Scrapy信息总结部分):

    {'downloader/request_bytes': 166187,
     'downloader/request_count': 500,
     'downloader/request_method_count/GET': 500,
     'downloader/response_bytes': 18988798,
     'downloader/response_count': 500,
     'downloader/response_status_count/200': 500,
     'finish_reason': 'finished',
     'finish_time': datetime.datetime(2018, 10, 16, 9, 49, 15, 761487),
     'item_scraped_count': 500,
     'log_count/DEBUG': 1001,
     'log_count/INFO': 8,
     'response_received_count': 500,
     'scheduler/dequeued': 500,
     'scheduler/dequeued/memory': 500,
     'scheduler/enqueued': 500,
     'scheduler/enqueued/memory': 500,
     'start_time': datetime.datetime(2018, 10, 16, 9, 48, 44, 58673)}
    

    可以看到,已成功爬取500个网页,耗时31秒,速度也相当OK。再来看一下生成的wiki.csv文件,它包含了所有的输出的name和description,如下图:

    可以看到,输出的CSV文件的列并不是有序的。至于如何解决Scrapy输出的CSV文件有换行的问题,请参考stackoverflow上的回答:https://stackoverflow.com/questions/39477662/scrapy-csv-file-has-uniform-empty-rows/43394566#43394566

      Scrapy来制作爬虫的优势在于它是一个成熟的爬虫框架,支持异步,并发,容错性较好(比如本代码中就没有处理找不到name和description的情形),但如果需要频繁地修改中间件,则还是自己写个爬虫比较好,而且它在速度上没有超过我们自己写的异步爬虫,至于能自动导出CSV文件这个功能,还是相当实在的。

    总结

      本文内容较多,比较了4种爬虫方法,每种方法都有自己的利弊,已在之前的陈述中给出,当然,在实际的问题中,并不是用的工具或方法越高级就越好,具体问题具体分析嘛~
      本文到此结束,感谢阅读哦~

    注意:本人现已开通微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注哦~~

    展开全文
  • 一、最简单的爬虫python3 爬虫小白系列文章)

    万次阅读 多人点赞 2018-06-23 22:56:53
    看了崔老师的python3网络爬虫实战,受益匪浅,为了帮助自己更好的理解这些知识点,于是打算趁着这股热乎劲,针对爬虫实战进行一系列的教程。 阅读文章前,我会默认你已经具备一下几个要素 1.python3安装完毕 ...
  • 手把手教你利用爬虫爬网页(Python代码)

    万次阅读 多人点赞 2019-05-14 14:51:58
    本文主要分为两个部分:一部分是网络爬虫的概述,帮助大家详细了解网络爬虫;另一部分是HTTP请求的Python实现,帮助大家了解Python中实现HTTP请求的各种方式,以...
  • Python3网络爬虫快速入门实战解析

    万次阅读 多人点赞 2020-04-23 09:58:13
    请在电脑的陪同下,阅读本文。本文以实战为主,阅读过程如稍有不适,还望多加练习。 本文的实战内容有:网络小说下载(静态网站)、优美壁纸下载(动态网站)、爱奇艺VIP视频下载 PS:本文为Gitchat线上分享文章,该文章...
  • Python/打响2019年第一炮-Python爬虫入门(一)

    万次阅读 多人点赞 2019-04-07 13:22:22
    打响2019第一炮-Python爬虫入门   2018年已经成为过去,还记得在2018年新年写过一篇【Shell编程】打响2018第一炮-shell编程之for循环语句,那在此时此刻,也是写一篇关于编程方面,不过要比18年的稍微高级点。 So...
  • Python爬虫的用途

    万次阅读 多人点赞 2018-08-16 14:02:03
    Python爬虫是用Python编程语言实现的网络爬虫,主要用于网络数据的抓取和处理,相比于其他语言,Python是一门非常适合开发网络爬虫的编程语言,大量内置包,可以轻松实现网络爬虫功能。 Python爬虫可以做的事情很多...
  • 32个Python爬虫项目让你一次吃到撑

    万次阅读 多人点赞 2020-05-19 15:22:39
    今天为大家整理了32个Python爬虫项目。 整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心。所有链接指向GitHub,祝大家玩的愉快~O(∩_∩)O WechatSogou [1]- 微信公众号爬虫。基于搜狗微信搜索的...
  • Python的火爆,同时也带动了Python爬虫岗位的的极大需求。可能有些人想问了,学Python爬虫真的好找工作吗?要学到什么程度?根据各大招聘网站的数据反馈,目前市场上对Python爬虫工程师的需求比较大。但是这并不意味...
  • Python爬虫(二):爬虫获取数据保存到文件

    万次阅读 多人点赞 2019-07-31 09:37:48
    接上一篇文章:Python爬虫(一):编写简单爬虫之新手入门 前言: 上一篇文章,我爬取到了豆瓣官网的页面代码,我在想怎样让爬取到的页面显示出来呀,爬到的数据是html页面代码,不如将爬取到的代码保存到一个文件...
  • Python爬虫实战视频教程

    千人学习 2020-02-25 17:04:03
    本课程使用Python3作为编程语言,主要内容包括Python爬虫的基本原理,编写简单的爬虫,使用爬虫从百度下载比基尼美女图片、beautiful soup的详细使用方法,如何使用beautiful soup分析html代码,基于队列的爬虫、...
  • Python简单易学,对编程初学者十分友好,而且具有丰富而强大的库,开发效率奇高,因此很多编程爱好者都对Python爬虫十分感兴趣。要知道学好爬虫对工作大有裨益,可为今后入门大数据分析、挖掘、机器学习等领域提供...
  • Python爬虫实战之爬取网站全部图片(一)

    万次阅读 多人点赞 2020-03-01 10:03:41
    Python爬虫实战之爬取网站全部图片(二) 传送门: https://blog.csdn.net/qq_33958297/article/details/89388556 爬取网址: http://www.meizitu.com/a/more_1.html 爬取地址:...
  • 小白学 Python 爬虫(25):爬取股票信息

    万次阅读 多人点赞 2019-12-25 13:34:21
    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Linux基础入门 小白学 Python 爬虫(4):...
  • 原计划继续写一下关于手机APP的爬虫,结果发现夜神模拟器总是卡死,比较懒,不想找原因了,哈哈,所以接着写后面的博客了,从50篇开始要写几篇python爬虫的骚操作,也就是用Python3通过爬虫实现一些小工具。...
  • 第二弹!python爬虫批量下载高清大图

    万次阅读 多人点赞 2019-10-22 00:20:19
    python爬虫绕过限制一键搜索下载图虫创意图片!中,我们在未登录的情况下实现了图虫创意无水印高清小图的批量下载。虽然小图能够在一些移动端可能展示的还行,但是放到pc端展示图片太小效果真的是很一般!建议阅读...
  • 无论是从入门级选手到专业...Python爬虫可以自学吗,有哪些好的书籍推荐?  1、如果你用Python3写爬虫,强力推荐《Python网络数据采集》这本书,应该是目前最系统最完善介绍Python爬虫的书。可以去图灵社区买电子...
  • python爬虫入门教程(二):开始一个简单的爬虫

    万次阅读 多人点赞 2019-10-28 14:38:45
    python爬虫入门教程,介绍编写一个简单爬虫的过程。
1 2 3 4 5 ... 20
收藏数 159,481
精华内容 63,792
关键字:

python爬虫