用python3的pyspider爬取国家统计局的行政区域(只到乡镇级)

版权声明:转载请注明出处 https://blog.csdn.net/hawk_2016/article/details/80547250

刚开始学习爬虫,学了pyspider,就想练练手。
想到不久前需要一些云南地区的行政数据,还是在网络百度半天才下载的。
现在既然会爬虫了,那就自己动手,随时可以用相对新的数据了(因为在我爬取时,统计局最新的才更新到2016年)。
行政划分的总体结构是:

省->市(州)->县(区)->乡镇(办事处)->村委会(社区)

国家统计局的网站基本没有反爬虫,挺适合我们新手操作哦!

正式开始编写爬虫的时候还是百度了半天的,中间也走过一点弯路的,其中在把结果写入mysql的过程中有地方会报错:
1、Mysql中文乱码问题:
解决办法就是修改my.ini里的内容(my.ini还是在安装路径下搜索吧),具体添加部分如下:

[client]
default-character-set=utf8
# pipe=

# socket=MYSQL

port=3306

[mysql]
no-beep
default-character-set=utf8
# default-character-set=


# SERVER SECTION
# ----------------------------------------------------------------------
#
# The following options will be read by the MySQL Server. Make sure that
# you have installed the server correctly (see above) so it reads this 
# file.
#
# server_type=3
[mysqld]
character-set-server=utf8

2、pyspider的config设置问题
按照如下代码新建的config.json文件,同时把文件放在了pyspider(安装好的库文件包)的文件夹下,但是运行pyspider -c config.json all 还是报错找不到路径。
**解决办法:**在cmd语句切换到pyspider的路径后,再用pyspider -c config.json all。
备注:pyspider的路径…\Lib\site-packages\pyspider
如果嫌麻烦,也可以直接把代码放在crawl_config = {}里边了。

{
        "taskdb": "mysql+taskdb://root:[email protected]:3306/taskdb",
        "projectdb": "mysql+projectdb://root:[email protected]:3306/projectdb",
        "resultdb": "mysql+resultdb://root:[email protected]:3306/resultdb",
        "message_queue": "redis://127.0.0.1:6379/db",
        "phantomjs-proxy": "127.0.0.1:25555",
        "scheduler" : {
        "xmlrpc-host": "0.0.0.0",
        "delete-time": 3600
         },
         "webui": {
         "port": 5000,
         "username": "hawk",
         "password": "mima",
         "need-auth": "true"
         }

简述完了,开始我们的爬虫之旅吧!

一、准备工作
1、安装pyspider、pymysql;(不会的请百度python安装模块)
2、新建3个mysql的库,为了防止新建的库不是utf编码的,我们还是用如下语句创建吧。

CREATE DATABASE `taskdb`  
CHARACTER SET 'utf8'  
COLLATE 'utf8_general_ci'

CREATE DATABASE `projectdb`  
CHARACTER SET 'utf8'  
COLLATE 'utf8_general_ci'

CREATE DATABASE `resultdb`  
CHARACTER SET 'utf8'  
COLLATE 'utf8_general_ci'

CREATE TABLE `zones` (  
`CodeNo` varchar(20) NOT NULL default '',  
`ZoneName` varchar(40) NOT NULL default '',  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

3、百度学习一下CSS选择器的基本用法(# . : a nth-child),这是解析网页的基本功哦。
4、由于下钻后的网页是使用的相对网址,故需要拼接成一个绝对网址:

from urllib.parse import urljoin
urljoin(self.base_url, 遍历的相对url)

二、开始爬虫
pyspider的用法,百度一下就很多,这里不再详述,可以参考这个博客:
https://www.jianshu.com/p/1f166f320c66
在新建好的project后,系统会自动生成一个简单的爬虫结构(起始页->index页->detail页)。一开始我就被这个结构框住了,限定了思维。其实,我们完全可以不用管这个结构,主要利用这个调用结果来遍历网址。其他就当一般的python语句来编写了。

for each in response.doc('.citytr').items():
    self.crawl()

注意:行政区域的网页不像一般例子,不存在detail页,因为每一次下钻网页返回的都是table格式的数据,不是返回的明细网页。我一开始就是这样去套这结构,结果就是浪费一些时间。

三、直接上代码
cursor.close()
self.db.close()
注意:这两个地方的语句是我运行完结果后加上去的,为了结束后关闭连接。但是我没有运行测试,如果报错,请删除后运行或者自行修改。
如果还要爬取到最末一级村委会,可以在下边的语句增加一个页面方法即可,具体怎么加?只能劳烦您举一反三了。

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2018-06-01 09:28:18
# Project: zones_yunnan

from pyspider.libs.base_handler import *
from urllib.parse import urljoin
import pymysql

class Handler(BaseHandler):
    #headers\cookies亦可以放入起始设置中
    crawl_config = {
        "taskdb": "mysql+taskdb://root:[email protected]:3306/taskdb",
        "projectdb": "mysql+projectdb://root:[email protected]:3306/projectdb",
        "resultdb": "mysql+resultdb://root:[email protected]:3306/resultdb",
        "message_queue": "redis://127.0.0.1:6379/db",
        "phantomjs-proxy": "127.0.0.1:25555",
        "scheduler" : {
        "xmlrpc-host": "0.0.0.0",
        "delete-time": 3600
         },
         "webui": {
         "port": 5000,
         "username": "hawk",
         "password": "mima",
         "need-auth": "true"
         }
    }
    
    
    #初始化基础页url
    def __init__(self):
        self.base_url = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/53.html'
        self.db = pymysql.connect(host="localhost",user="root", 
    password="mysql_2018",db="resultdb",port=3306,charset='utf8') 
        
    #定义一个mysql的insert方法,方便后边写码
    def insert_zone(self, code, zone_name):
        try:
            cursor = self.db.cursor()
            sql_insert = 'insert into zones(CodeNo,ZoneName) values ("%s","%s")' % (code, zone_name);
            cursor.execute(sql_insert)
            self.db.commit()
        except Exception as e:
            print(e)
            self.db.rollback() 
            cursor.close()
            self.db.close()
    
    
    #crawl的参数:priority值越大越优先;fetch_type='js'即可以js渲染;还有use_agent\headers\cookies\load_images.
    @every(minutes=24 * 60)
    def on_start(self):
        self.crawl(self.base_url, callback=self.city_page)
    
    @config(age=10 * 24 * 60 * 60)
    def city_page(self, response):
        for each in response.doc('.citytr').items():
            self.crawl(urljoin(self.base_url, each.find('td a:last-child').attr('href')), callback=self.county_page)
            code=each.find('td:first-child').text()
            city=each.find('td:last-child').text()
            self.insert_zone(code,city)
                     

    @config(age=10 * 24 * 60 * 60)
    def county_page(self, response):
        for each in response.doc('.countytr').items():
            self.crawl(urljoin(self.base_url, each.find('td a:last-child').attr('href')), callback=self.town_page)
            code=each.find('td:first-child').text()
            county=each.find('td:last-child').text()
            self.insert_zone(code,county)

    @config(age=10 * 24 * 60 * 60,priority=2)
    def town_page(self, response):
        baseurl = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/'
        for each in response.doc('.towntr').items():
            #self.crawl(urljoin(baseurl ,each.find('td a:last-child').attr('href')), #callback=self.town_page)
            code=each.find('td:first-child').text()
            town=each.find('td:last-child').text()
            self.insert_zone(code,town)
     

四、查看结果
Mysql的查询结果是不会自动返回行号的,得手工添加一个变量:

SELECT @rowno:=@rowno+1 as rowno,r.* from zones r,(select @rowno:=0) t

这里写图片描述

猜你喜欢

转载自blog.csdn.net/hawk_2016/article/details/80547250