scrapy wikipedia country
http://github.com/1012598167/scrape
实验目的
爬虫实习的项目1,利用python的scrapy框架爬取维基百科(英文)的国家信息。
实验要求
-
- 网址:https://en.wikipedia.org/
- 描述:根据用户输入国家名称搜索,爬取结果页面中国家信息。
- 输入:国家名称。
- 输出:国家信息。
- 字段:国家名称#Motto#Anthem#National song#Capital#Largest city#…#Internet TLD#更多信息。
- 要求:字段比较不统一,除上述字段外,请先搜索几十个主要国家,看下包含哪些字段,把最大字段集合确定下来,然后开始写爬虫。某国家不存在某字段,则该字段为空字符串。调研后可以与我讨论字段集合。
实验过程
-
conda install scrapy
-
选择一个文件夹,打开poweshell
-
mkdir internship\ spider\ scripy cd internship\ spider\ scripys scripy startproject wiki_country scripy genspider countryspider scripy list#看有没有生成这个spider 结果有countryspider
在result中有wiki.py
-
之后在wiki_country文件下放querys和results文件存输入和输出
文件目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvjsUmoV-1611036513794)(https://github.com/1012598167/my-tuchuang/raw/master/1560350953127.png)]
第一个子文件是github仓库,第二个是scrapy的文件目录格式。
中间过程
本来只有一个wiki.py就行了,但是因为一些手动输入标签会产生一些乱码:
小部分可以使用.replace()函数,并且将编码设置为与网站源码相同,比如 #coding:utf-8
然后对于一些‘GDP (nominal)’(wiki Americas的标签)这中间这个空格会在utf-8下出现各种形式,比如‘ ’(\xa0),’ ‘(\xc2\xa0),‘聽’(’\xe8\x81\xbd’),‘鑱?’(’\xe9\x91\xb1?’),一种在不同软件的utf-8打开会改变utf-8编码甚至切换成斜体的空格:
Area,Population,GDP (nominal),HDI,Demonym,Countries,Languages,Time zones
如这边的’GDP (nominal)’(标签产生于https://en.wikipedia.org/wiki/Americas)中的空格,就是上述类型的空格,您可以试着爬该网站的标签至json文件,发现其空格与之后Time zones的空格完全不等长。
解决:可以初步尝试打开这个输出结果并重新replace一下,再f.close(),也可以试着直接爬下标签(或国家名)之后再爬对应的值,省去中间步骤,即不要自己输键而把键也直接爬下来(标签/国家名)。
所以我试着自己从头爬国家,爬标签并且爬值(对应三个spider)。
同时我用了三种写法写着三个spider:
-
爬国家:单个py文件使用scripy runspider country.py就可直接当成spider crawl
-
爬标签:在startproject产生的spiders下只改label.py(输入输出都在里面做)
scrapy crawl label
-
爬值:修改了item与pipeline,以规范化+实现去重与排列
scrapy crawl countryspider
这样三个流程如流水线一般,就不需要手动参与,比如手动输入标签,引起乱码之类的后果。
整个运行可通过bash a.sh来完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z31fwOyP-1611036513796)(https://github.com/1012598167/my-tuchuang/raw/master/1560351889955.png)]
并且爬标签时进行了去重和最常见40个标签排列,并直接写在pipeline内而不进行多余的txt/json文件处理。
代码
注释写得很全了,细节不多解释。
Talk is cheap, show me the code!!
country.py:
# -*- coding: utf-8 -*-
import scrapy
import json
#import time
import re
class CountrySpider(scrapy.Spider):
name = ‘countryspider’
def start_requests(self):
path = ‘https://www.worldometers.info/geography/alphabetical-list-of-countries/’
yield scrapy.Request(url=path ,callback=self.parse)#以parse方式发出request
#meta={‘country_name’:query}
def parse(self, response):
countrys = response.xpath('//td[@style="font-weight: bold; font-size:15px"]/text()').extract()
input_='{"query":['
for country in countrys:
#print('###########'+country)
input_=input_+'"'+'_'.join(country.split(' ')).replace('\xa0', ' ')+'",'
input_=input_[:-1]+']}'
f_name='./wiki_country/querys/input.json'
fw = open(f_name, 'w',encoding='utf-8')#覆盖写
fw.write(input_)
fw.close()
注:通过爬去常见国家列表’https://www.worldometers.info/geography/alphabetical-list-of-countries/’ 生成国家文件input.json, 注意json存的是字典!
修改的items.py
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class WikiCountryItem(scrapy.Item):
# define the fields for your item here like:
label_num_dict=scrapy.Field()#排序完的结果 是一个列表!
pass
注:这个item是一个字典!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a5WXJmCp-1611036513801)(https://github.com/1012598167/my-tuchuang/raw/master/1560352996453.png)]
label.py:
# -*- coding: utf-8 -*-
import scrapy
import json
#import time
import re
from ..items import WikiCountryItem
class LabelSpider(scrapy.Spider):
name = 'label'
def __init__(self):
self.items={}
def start_requests(self):
with open(r'wiki_country/querys/input.json', 'r') as f:
querys = json.load(f) # 把国家load进来
path = 'http://en.wikipedia.org/wiki/'
for query in querys['query']:
url = path + '_'.join(query.split(' ')) # 对于比如United States去空格为_
print(url)
yield scrapy.Request(url=url, callback=self.parse) # 以parse方式发出request
def parse(self, response):
fields = response.css('.infobox').xpath('.//tr[not(contains(@class, "mergedrow") or contains(@class, "mergedbottomrow"))]/th[@scope="row"]')
# infobox中获取标签如Area Population 不能含比如'• 4th Republic of Albania'的子标题
#res = {}
for field in fields:
item=WikiCountryItem()
key = field.xpath('.//text()').extract_first().replace('\xa0', ' ') # 对应一个个标签比如Area
if key not in self.items:
self.items[key]=1
#以此去重 若在字典里就不再加一遍
else:
self.items[key]+=1
#print('self.items:',self.items)
#item['label_num_dict']=sorted(self.items.keys())
item['label_num_dict'] =sorted(self.items.items(), key=lambda item: item[1], reverse=True)#按出现多少的顺序输出
yield item
#print("item['label_num_dict']",item['label_num_dict'])# #把标签多的放在前面输出 这是一个列表!
流程 一个request一个response,response完了给pipeline处理,pipeline return了再回来下一个request。
注:由于有些国家会有这样的情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wegzmzan-1611036513803)(https://github.com/1012598167/my-tuchuang/raw/master/1560352586598.png)]
造成结果有一堆点之类不需要的结果
比如
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FfxTyPFj-1611036513806)(https://github.com/1012598167/my-tuchuang/raw/master/1560267793956.png)]
所以这里xpath有细改过。
pipeline.py:
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
class WikiCountryPipeline(object):
#def __init__(self):
# self.filename = open(r'wiki_country/querys/fields.txt','w+', encoding='utf-8')#因为写的列表,所以覆盖写
def process_item(self, item, spider):
#if item['label']:
# self.filename.write(item['label']+',')
#self.filename.close()
#f2 = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')#覆盖写
# for label_ in item['label_num_dict']:
# self.filename.write(label_ + ',')
if spider.name=='label':
filename = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')#因为写的列表,所以覆盖写
count = 0
for tuple_ in item['label_num_dict']:
filename.write(str(tuple_[0]))
count+=1
if count>40:#取前40个输出
break
else:
filename.write(',')
filename.close()
#field_set = self.filename.read().split(',')[:-1]#去掉最后一个逗号的空的
return item
#def close_spider(self,spider):#只在所有爬完的时候才运行
#filename = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')
#取前四十个的字段输出并结束
#field_set = listself.filename.read()
#count=0
#self.filename.close()
#下面 吃掉最后一个逗号
# self.filename = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')
# field_set = self.filename.read()[:-1]
# self.filename.write(field_set)
# self.filename.close()
注:实时根据现有的标签排名更改输出 取前40个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWqAtLJa-1611036513807)(C:\Users\mathskiller\Pictures\my-tuchuang\1560352732272.png)]
settings.py:
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'wiki_country.pipelines.WikiCountryPipeline': 300,
}
一定要取消注释!不然pipeline没用!害了我很久!
wiki.py:
# -*- coding: utf-8 -*-
#上一行为了中文注释
#由于在缺陷中提到的那个乱码问题,我在\scrapy_country_and_label文件夹下顺便爬了国家和标签
import scrapy
import json
import time
import re
# def deal_luanma():
# f = open('Americas-.txt','w+', encoding='utf-8')
# res=(f.read().replace('聽', ' '))
# f.write(res)
# f.close()
class CountrySpider(scrapy.Spider):
name = 'countryspider'
def start_requests(self):
with open(r'wiki_country/querys/input.json', 'r') as f:
querys = json.load(f)#把国家load进来
#print("###"+querys)
path = 'http://en.wikipedia.org/wiki/'
for query in querys['query']:
url = path + '_'.join(query.split(' '))#对于比如United States去空格为_
print(url)
yield scrapy.Request(url=url ,callback=self.parse)#以parse方式发出request
#meta={'country_name':query}
def parse(self, response):
fields = response.css('.infobox').xpath('.//th[@scope="row"]')#infobox中获取标签如Area Population
res = {}
for field in fields:
key = field.xpath('.//text()').extract_first()#对应一个个标签比如Area
#' ' 是一种奇怪的空格???????????????/怎么办
value = field.xpath('string(../td)').extract_first().replace('\xa0', ' ')
#string可以解决有很多值 把所有值都提取
#获取标签对应值,比如Area是4254900m平方米 如果有空格符就换成一般空格
value = re.sub(r'\[(\d{1,2})\]','',value)
#维基会有类似论文中的上标表示出处,将其删去
res[key] = value#res生成关于infobox标签的键值对'
f = open(r'wiki_country/querys/fields.txt',encoding='utf-8')
#ff='Area,Population,GDP (nominal),HDI,Demonym,Countries,Languages,Time zones'
field_set = f.read().split(',')#读入标签并分割成集合
#field_set = ff.split(',')
country_name = response.css('.firstHeading::text').extract_first()#大标题 也就是国家名字
#timestamp= str(int(time.time()))#获取系统时间
res_path=r'wiki_country/results'
f_name=res_path+'\\'+country_name+'.txt'#要导入结果的文件名
fw = open(f_name, 'w',encoding='utf-8')#覆盖写
fw.write('CountryName')
fw.write('#' + '#'.join(field_set) + '\n')#把标签献给文件
fw.write(country_name)
for field in field_set[:]:#准备写值
fw.write('#')
if field in res:#有值就写没值就过
fw.write(res[field].replace('\n',''))#多行的可能会有\n
fw.close()
f.close()
中间版本
由于刚开始做的时候忘记传github了,所以中间很多写法都没了,亏死了555
这里仅存了一种非常复杂的写法,是我刚开始没有逻辑时的智障写法,可以引以为戒。
# -*- coding: utf-8 -*-
import scrapy
import json
#import time
import re
from ..items import WikiCountryItem
class LabelSpider(scrapy.Spider):
name = 'label'
def __init__(self):
self.items={}
def start_requests(self):
with open(r'wiki_country/querys/input.json', 'r') as f:
querys = json.load(f) # 把国家load进来
path = 'http://en.wikipedia.org/wiki/'
for query in querys['query']:
url = path + '_'.join(query.split(' ')) # 对于比如United States去空格为_
print(url)
yield scrapy.Request(url=url, callback=self.parse) # 以parse方式发出request
def parse(self, response):
fields = response.css('.infobox').xpath('.//th[@scope="row"]') # infobox中获取标签如Area Population
#res = {}
for field in fields:
field=field.replace('\xa0', ' ')
item=WikiCountryItem()
key = field.xpath('.//text()').extract_first() # 对应一个个标签比如Area
if item not in self.items:
self.items[key]=1
#以此去重 若在字典里就不再加一遍
else:
self.items[key]+=1
print('self.items:',self.items)
item['label_num_dict']=sorted(self.items.keys())
print("item['label_num_dict']",item['label_num_dict'])#把标签多的放在前面输出 这是一个列表!
旧pipeline.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
class WikiCountryPipeline(object):
def __init__(self):
self.filename = open(r'wiki_country/querys/fields.txt','w+', encoding='utf-8')#因为写的列表,所以覆盖写
def process_item(self, item, spider):
#if item['label']:
# self.filename.write(item['label']+',')
#self.filename.close()
#f2 = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')#覆盖写
for label_ in item['label_num_dict']:
self.filename.write(label_ + ',')
#field_set = self.filename.read().split(',')[:-1]#去掉最后一个逗号的空的
return item
def close_spider(self,spider):
self.filename.close()
#下面 吃掉最后一个逗号
self.filename = open(r'wiki_country/querys/fields.txt', 'w+', encoding='utf-8')
field_set = self.filename.read()[:-1]
self.filename.write(field_set)
self.filename.close()
实验效果
input.json
{“query”:[“Afghanistan”,“Albania”,“Algeria”,“Andorra”,“Angola”,“Antigua_and_Barbuda”,“Argentina”,“Armenia”,“Australia”,“Austria”,“Azerbaijan”,“Bahamas”,“Bahrain”,“Bangladesh”,“Barbados”,“Belarus”,“Belgium”,“Belize”,“Benin”,“Bhutan”,“Bolivia”,“Bosnia_and_Herzegovina”,“Botswana”,“Brazil”,“Brunei_”,“Bulgaria”,“Burkina_Faso”,“Burundi”,“Côte_d’Ivoire”,“Cabo_Verde”,“Cambodia”,“Cameroon”,“Canada”,“Central_African_Republic”,“Chad”,“Chile”,“China”,“Colombia”,“Comoros”,“Congo_(Congo-Brazzaville)”,“Costa_Rica”,“Croatia”,“Cuba”,“Cyprus”,“Czechia”,“Democratic_Republic_of_the_Congo”,“Denmark”,“Djibouti”,“Dominica”,“Dominican_Republic”,“Ecuador”,“Egypt”,“El_Salvador”,“Equatorial_Guinea”,“Eritrea”,“Estonia”,“Ethiopia”,“Fiji”,“Finland”,“France”,“Gabon”,“Gambia”,“Georgia”,“Germany”,“Ghana”,“Greece”,“Grenada”,“Guatemala”,“Guinea”,“Guinea-Bissau”,“Guyana”,“Haiti”,“Holy_See”,“Honduras”,“Hungary”,“Iceland”,“India”,“Indonesia”,“Iran”,“Iraq”,“Ireland”,“Israel”,“Italy”,“Jamaica”,“Japan”,“Jordan”,“Kazakhstan”,“Kenya”,“Kiribati”,“Kuwait”,“Kyrgyzstan”,“Laos”,“Latvia”,“Lebanon”,“Lesotho”,“Liberia”,“Libya”,“Liechtenstein”,“Lithuania”,“Luxembourg”,“Madagascar”,“Malawi”,“Malaysia”,“Maldives”,“Mali”,“Malta”,“Marshall_Islands”,“Mauritania”,“Mauritius”,“Mexico”,“Micronesia”,“Moldova”,“Monaco”,“Mongolia”,“Montenegro”,“Morocco”,“Mozambique”,“Myanmar_(formerly_Burma)”,“Namibia”,“Nauru”,“Nepal”,“Netherlands”,“New_Zealand”,“Nicaragua”,“Niger”,“Nigeria”,“North_Korea”,“North_Macedonia”,“Norway”,“Oman”,“Pakistan”,“Palau”,“Palestine_State”,“Panama”,“Papua_New_Guinea”,“Paraguay”,“Peru”,“Philippines”,“Poland”,“Portugal”,“Qatar”,“Romania”,“Russia”,“Rwanda”,“Saint_Kitts_and_Nevis”,“Saint_Lucia”,“Saint_Vincent_and_the_Grenadines”,“Samoa”,“San_Marino”,“Sao_Tome_and_Principe”,“Saudi_Arabia”,“Senegal”,“Serbia”,“Seychelles”,“Sierra_Leone”,“Singapore”,“Slovakia”,“Slovenia”,“Solomon_Islands”,“Somalia”,“South_Africa”,“South_Korea”,“South_Sudan”,“Spain”,“Sri_Lanka”,“Sudan”,“Suriname”,“Swaziland”,“Sweden”,“Switzerland”,“Syria”,“Tajikistan”,“Tanzania”,“Thailand”,“Timor-Leste”,“Togo”,“Tonga”,“Trinidad_and_Tobago”,“Tunisia”,“Turkey”,“Turkmenistan”,“Tuvalu”,“Uganda”,“Ukraine”,“United_Arab_Emirates”,“United_Kingdom”,“United_States_of_America”,“Uruguay”,“Uzbekistan”,“Vanuatu”,“Venezuela”,“Vietnam”,“Yemen”,“Zambia”,“Zimbabwe”]}
fields.txt:
GDP,Capital,Government,Time zone,Currency,Driving side,Calling code,Internet TLD,Demonym(s),Legislature,ISO 3166 code,HDI,Official languages,Gini,Ethnic groups,Religion ,Date format,National language,Official language,Romanization,Spoken languages,IPA,Other languages,Official script,Hanyu Pinyin,Yale Romanization,National languages,Vernacular language,Official scripts,Vernacular,Inter-ethnic,Official language ,Simplified Chinese,Traditional Chinese,Bopomofo,Gwoyeu Romatzyh,Wade–Giles,Tongyong Pinyin,MPS2,Xiao’erjing,Pha̍k-fa-sṳ
results
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DtBuWXaJ-1611036513809)(https://github.com/1012598167/my-tuchuang/raw/master/1560353464771-1560411815676.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CR1dpbmE-1611036513810)(https://github.com/1012598167/my-tuchuang/raw/master/1560353503631.png)]
可以看到现在就没有乱码了
改版
现在的github有两份文件,一份wiki_country,一份wiki_company
wiki_country是主要文件 wiki_company:
这份company是基于country改的 只需要花一小时不到就能改成爬wiki的任何东西 但是:
爬的的确是company但是代码结构 文件地址 变量名称 注释 README都是country
所以若您对这份代码感兴趣,您可以先试着改成爬wiki的其他网站,再者爬任何静态网页。
致谢
真的感谢王佳伟同学,我本来应该是爬公司的,他爬国家,做了五天才发现我做错了。。羞愧不已,王佳伟同学愿意在已经做完的情况下跟我换任务,给我腾出时间取赶其他ddl,感激不尽,好人一生平安!