一、知识点:
1.urljoin
response.urljoin():将相对网址拼接成绝对网址。
比如:
url = response.body_as_unicode() url = response.urljoin(url)
这样就能在url前拼接上https:
二、步骤
1.新建爬虫
scrapy startproject travalcity cd travalcity scrapy genspider travelspider travel.cn
2.新建Item (决定抓取哪些项目)
class TravalcityItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field() #标题
desc =scrapy.Field() #简介
3.编写爬虫文件travelspider.py (决定怎么爬)
import scrapy
from scrapy.selector import Selector
from travalcity.items import *
class TravelspiderSpider(scrapy.Spider):
name = "travelspider"
allowed_domains = ["bytravel.cn"]
start_urls = ['http://wap.bytravel.cn/view/index3480_list.html']
def parse(self, response):
for href in response.xpath("//ul[@id='titlename']/li/a/@href").extract():
item=TravalcityItem()
href = "http://wap.bytravel.cn"+href
print(href)
request=scrapy.http.Request(response.urljoin(href),callback=self.parse_desc)
request.meta['item']=item
yield request
def parse_desc(self,response):
item=response.meta['item']
item['title']=response.xpath("//h1").extract()
item['desc']=response.xpath("//article/div[3]").extract()
yield item
如果用xpath提取的url不是完整的域名,这时就需要使用urljoin进行拼接,传递任何url给urljoin可以将url的主域名取出来。
附另外一种写法:
from scrapy.http import Request from urllib import parse #python3用法 for post_url in post_urls: yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
全代码:
import scrapy
from scrapy.selector import Selector
from travalcity.items import *
from urllib import parse
class TravelspiderSpider(scrapy.Spider):
name = "travelspider"
allowed_domains = ["bytravel.cn"]
start_urls = ['http://wap.bytravel.cn/view/index3480_list.html']
def parse(self, response):
for href in response.xpath("//ul[@id='titlename']/li/a/@href").extract():
# href = "http://wap.bytravel.cn"+href
print(href)
request=scrapy.http.Request(url=parse.urljoin(response.url,href),callback=self.parse_desc)
yield request
next_page=response.xpath("//nav[@id='list-page']/ul/li[last()-0]/a/@href").extract_first()
last_page=response.xpath("//nav[@id='list-page']/ul/li[last()-1]/a/@href").extract_first()
if last_page:
next_page="http://wap.bytravel.cn/view/"+next_page
yield scrapy.http.Request(next_page,callback=self.parse)
def parse_desc(self,response):
item=TravalcityItem()
item['title']=response.xpath("//h1/text()").extract()[0]
item['desc'] = response.xpath("//article/div[3]/text()").extract()+response.xpath("//article/div/p/text()").extract()
tempItem = ""
for x in item['desc']:
x =x.replace('\r\n','')
x =x.replace('\r\n\r\n\r\n','')
tempItem = tempItem + x
item['desc']= tempItem
item['province']=response.xpath('//div[@id="mainbao"]/a[2]/text()').extract()[0]
item['city']=response.xpath('//div[@id="mainbao"]/a[3]/text()').extract()[0]
yield item
4、测试
在“C:\Users\Kevin\travelspider\travalcity\travalcity>”文件夹下面执行下面的命令:
scrapy crawl travelspider -0 woodenrobot.csv scrapy crawl travelspider -0 woodenrobot.json
注意,是o,不是0。
发现通过上面的输出,输出的csv乱码,输出的json也是编码不对。

这里只需要加上参数就可以了:
scrapy crawl travelspider -o woodenrobot.json -s FEED_EXPORT_ENCODING='utf-8'
5.修改pipilines.py (决定爬取后的内容怎么样处理)
import sqlite3
import pymysql.cursors
class TravalcityPipeline(object):
def open_spider(self, spider):
self.con = pymysql.connect(
host='127.0.0.1',#数据库地址
port=3306,# 数据库端口
db='testscrapy', # 数据库名
user = 'root', # 数据库用户名
passwd='', # 数据库密码
charset='utf8', # 编码方式
use_unicode=True)
self.cu = self.con.cursor()
def process_item(self, item, spider):
print(spider.name, 'pipelines')
insert_sql = "insert into test (title,content) values('{}','{}')".format(item['title'], item['desc'])
print(insert_sql) # 为了方便调试
self.cu.execute(insert_sql)
self.con.commit()
return item
def spider_close(self, spider):
self.con.close()
6.修改settings.py (决定由谁去处理爬取的内容)
取消下面的代码的注释即可。
ITEM_PIPELINES = {
'travalcity.pipelines.TravalcityPipeline': 300,
}
8.查看效果
这里最好使用Navicat来建立数据库,并将id设为“自动递增”以及为主键。

备注:下面还可以设置默认值。

可以看到已经成功地插入到数据库。

9. shell测试
直接执行以下命令:
scrapy shell "http://wap.xx.com"
response.xpath("//h1/text()").extract()[0]

10.去除'/r/n'
采集的结果中有许多'/r/n',在网上找了解决方案,说是可以使用normalize-space这个函数,但是我使用这个函数后,发现抓取的数据不全,比如明明一篇文章,未使用这个函数可以抓取到全部文件,使用了这个函数之后,后面有两段文章抓取不到。所以最后只能用字符串替换的方法来解决。
11、url函数
因为我是分块抓取的,所以另外写了一个函数,再使用start_urls = readUrl()将所有url加入到了起始url。
def readUrl():
data = []
for line in open("url.txt","r"): #设置文件对象并读取每一行文件
line = line.replace("\n","")
data.append(line) #将每一行文件加入到list中
return data
三、使用SQlite保存数据
1.新建sqlite数据库及数据表
最好的方法是使用Navicat,方法如下:

这里选择“新建SQlite3”,并将数据库文件的存储位置设置为执行“scrapy crawl xxx”命令的文件夹。

打开刚刚建立的数据库,开始新建数据表

这里完成各种字段的设置之后,navicat会提示你输入表格的名称。另外,在这里可以选择的SQlite的数据类型只有4种,大大少于Mysql的数据类型。

当然也可以通过命令来建立:
import sqlite3
# test.db is a file in the working directory
conn = sqlite3.connect("test.db")
c = conn.cursor()
# create tables
sql = '''create table student (id int primary key, name varchar(20), score int, sex varchar(10), age int)'''
c.execute(sql)
# save the changes
conn.commit()
# close the connection with the database
conn.close()
可以参考这里:https://www.cnblogs.com/lmei/p/5322502.html
2.修改pipeline
import sqlite3
class Sqlite3Pipeline(object):
def __init__(self, sqlite_file, sqlite_table):
self.sqlite_file = sqlite_file
self.sqlite_table = sqlite_table
@classmethod
def from_crawler(cls, crawler):
return cls(
sqlite_file = crawler.settings.get('SQLITE_FILE'), # 从 settings.py 提取
sqlite_table = crawler.settings.get('SQLITE_TABLE', 'items')
)
def open_spider(self, spider):
self.conn = sqlite3.connect(self.sqlite_file)
self.cur = self.conn.cursor()
def close_spider(self, spider):
self.conn.close()
def process_item(self, item, spider):
insert_sql = "insert into travelcity (title,content,province,city) values('{}','{}','{}','{}')".format(item['title'], item['desc'],item['province'],item['city'])
#insert_sql = "insert into {} (title,content,province,city) values('{}','{}','{}','{}')".format(self.sqlite_table,item['title'], item['desc'],item['province'],item['city'])
self.cur.execute(insert_sql)
self.conn.commit()
return item
3.修改settings.py文件
SQLITE_FILE = 'example.db'
SQLITE_TABLE = 'travelcity'
ITEM_PIPELINES = {
'travalcity.pipelines.Sqlite3Pipeline': 300,
}
这时再运行爬虫就可以了。
可以参考:https://blog.csdn.net/weixin_34217711/article/details/90226081
4.在Navicat查询数据库的行数

5.关于数据丢失的问题
查看scrapy的报表发现丢失了许多数据,一直搞不懂是什么原因。

后来用两个列表页作测试,发现其中一个列表页丢失数据,加上twisted终于没有丢失数据了。
附最终代码:
import sqlite3
import pymysql.cursors
from scrapy import log
from twisted.enterprise import adbapi
class DbSqlitePipeline(object):
def __init__(self):
"""Initialize"""
self.__dbpool = adbapi.ConnectionPool('sqlite3',
database='example.db',
check_same_thread=False)
def shutdown(self):
"""Shutdown the connection pool"""
self.__dbpool.close()
def process_item(self,item,spider):
"""Process each item process_item"""
query = self.__dbpool.runInteraction(self.__insertdata, item, spider)
query.addErrback(self.handle_error)
return item
def __insertdata(self,tx,item,spider):
"""Insert data into the sqlite3 database"""
spidername=spider.name
tx.execute(\
"insert into worldcity(title,content,province,city) values (?,?,?,?)",(
item['title'],
item['desc'],
item['province'],
item['city'])
)
log.msg("Item stored in db", level=log.DEBUG)
def handle_error(self,e):
log.err(e)
参考:https://github.com/ritesh/sc/blob/master/scraper/pipelines.py
最终结果:
