2

Here is a minimal example of a flask view, which produces a CSV (python 2.7).

# -*- coding: utf-8 -*-

import csv
import StringIO

from flask import Flask, Response

app = Flask(__name__)


@app.route('/example.csv')
def example_csv():
    f = StringIO.StringIO()
    writer = csv.writer(f)
    writer.writerow(['Header 1', 'Header 2', 'Header 3'])
    writer.writerows([unicode(_).encode('utf-8') for _ in row] for row in (['1', '2', '3'], 
                                                                           ['a', 'b', 'c'],
                                                                           [u'£', u'€', u'¥']))
    response = Response(f.getvalue(), mimetype='text/csv')
    response.headers['Content-Disposition'] = u'attachment; filename=example.csv'
    return response


if __name__ == '__main__':
     app.run(debug=True)

Opening in Excel gives:

Header 1    Header 2    Header 3
1   2   3
a   b   c
£  € ¥

With Apple's Numbers App, the last line renders properly. How can I get Excel to render properly? Am I missing some sort of encoding setting?

user964375
  • 2,201
  • 3
  • 26
  • 27
  • Looking at the headers coming back when you request this endpoint, is the `Content-Type` set to `text/csv; charset=utf-8`? – Sean Vieira May 04 '16 at 03:03
  • Yes, ➜ ~ curl -v http://127.0.0.1:5000/example.csv * Trying 127.0.0.1... * Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0) > GET /example.csv HTTP/1.1 > Host: 127.0.0.1:5000 > User-Agent: curl/7.43.0 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Content-Type: text/csv; charset=utf-8 < Content-Length: 53 < Content-Disposition: attachment; filename=example.csv < Server: Werkzeug/0.10.4 Python/2.7.9 < Date: Wed, 04 May 2016 08:21:54 GMT < Header 1,Header 2,Header 3 1,2,3 a,b,c £,€,¥ * Closing connection 0 – user964375 May 04 '16 at 08:22

4 Answers4

5

Is it possible to force Excel recognize UTF-8 CSV files automatically? suggests that the issue is that Excel does not consider the file to be UTF-8 unless it starts with a Byte Order Mark (for reasons of backwards compatibility with its previous behavior).

Try adding an encoded BOM as the first three bytes of your response:

response = Response(u'\uFEFF'.encode('utf-8') + f.getvalue(), mimetype='text/csv')

Alternatively, the first answer suggests that if that doesn't work, shipping the contents as UTF-16 will often work (but not for every version of Excel, even then).

Community
  • 1
  • 1
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
3

This is an example of python3, create StringIO, write \uFEFF and then create csv writer to render correctly in Excel 2019.

This method also solves the problem of the Chinese not being rendered correctly.

flask version=2.0.1

    import csv
    from io import StringIO
    
    from flask import Flask, Response
    
    app = Flask(__name__)
    
    
    @app.route('/example.csv')
    def example_csv():
        def generate():
            f = StringIO()
            f.seek(0)
            f.write(u'\uFEFF')
            writer = csv.writer(f)
            writer.writerow(('Header 1', 'Header 2', 'Header 3'))
            dataset = [['1', '2', '3'],
                       ['a', 'b', 'c'],
                       ['£', '€', '¥'],
                       ['壹', '貳', '參']]
            for row in dataset:
                writer.writerow(tuple(row))
                yield f.getvalue()
                f.seek(0)
                f.truncate(0)
    
        response = Response(generate(), mimetype='text/csv')
        response.headers.set("Content-Disposition",
                             "attachment",
                             filename='example.csv')
        return response
    
    
    if __name__ == '__main__':
        app.run(debug=True)

Excel render thumbnail

References:

Create and download a CSV file from a Flask view

Is it possible to force Excel recognize UTF-8 CSV files automatically?

shiya
  • 31
  • 4
1

This works for me:

import codecs
from flask import make_response

output = make_response(
    codecs.BOM_UTF8.decode("utf8") + codecs.BOM_UTF8.decode() + f.getvalue())
output.headers["Content-Disposition"] = "attachment; filename=export.csv"
output.headers["Content-type"] = "text/csv"
return output
Yu-Lin Chen
  • 559
  • 5
  • 12
0

Try to use utf-8 with signature, so replacing "utf-8" with "utf-8-sig" should work.

In your code: unicode(_).encode('utf-8-sig')

Mahyar Zarifkar
  • 176
  • 1
  • 12