Split long thin faces into less elongated ones
I've done some research and tried a number of methods, so I'll document them here. Feel free to improve on my rather basic Python!
First some code that identifies the long edges, and bisects them (adds new vertices along the long edges:
import bpy
import bmesh
from mathutils import Vector

...

# There's only ever one object in the data I'm processing. Could also specify by name
# e.g. bpy.data.objects['surfboard'].data
me = bpy.data.objects[0].data
bm = bmesh.new()
bm.from_mesh(me)

EPSILON = 1.0e-3

...

longFaces=[]
edgesToCut={}
for vert in bm.verts:
    if abs(vert.co.x-0.05) &lt; EPSILON:
        for edge in vert.link_edges:
            leng=edge.calc_length()
            if leng &gt; 0.4:
                for face in edge.link_faces:
                    longFaces.append(face)
                cuts=int(leng/0.2)-1
                # Categorise according to number of cuts, for efficiency because 
                # bisect_edges can cut more than one edge at once.
                if cuts not in edgesToCut:
                    edgesToCut[cuts] = []
                edgesToCut[cuts].append(edge)

for cuts, edges in edgesToCut.items():
    bmesh.ops.bisect_edges(bm, edges=list(set(edges)), cuts=cuts)

This focusses on the edges connected to the x = 0.05cm line, which are the issue for me, and assumes that if a face has a long side, it must be thin, which is true for the cases I'm dealing with. This could be altered to suit, e.g. to actually measure the width and make cuts based on how thin the face is. But the idea is to find the problem faces and add extra vertices where they will be useful.
I use list(set()) to weed out duplicates.
Next, I could use
bmesh.ops.triangulate(bm, faces=list(set(longFaces)))

which will automatically split each face into triangles. However for me, that just creates lots of long and even thinner triangles than the faces I split, which actually makes the unwrapping worse. So I devised a manual solution by scanning the vertices on each of the faces and measuring to the nearest vertex on that face. If they're not already connected, then I can mark them for connection:
# For each face, test if each vertex is connected to its closest sibling
longFaces = list(set(longFaces))
vertsToJoin = []
for face in longFaces:
    edges = face.edges
    # Make a mapping of the vertices on each edge
    connectedVert = []
    lastVert = False
    firstVert = False
    for vert in face.verts:
        if lastVert:
            connectedVert.append((vert, lastVert))
            connectedVert.append((lastVert, vert))
        lastVert = vert
        if not firstVert:
            firstVert = vert
    # and join to the beginning
    connectedVert.append((firstVert, lastVert))
    connectedVert.append((lastVert, firstVert))
    for vert in face.verts:
        # What's the closest other vertex?
        minLength = 9999
        minVert = False
        for vert2 in face.verts:
            if vert != vert2:
                vec = Vector(vert2.co - vert.co)
                if vec.length &lt; minLength:
                    minLength = vec.length
                    minVert = vert2
        if minVert\
                and (vert, minVert) not in connectedVert\
                and (vert, minVert) not in vertsToJoin\
                and (minVert, vert) not in vertsToJoin:
            # Only join if they're the closest and they're not connected already
            vertsToJoin.append((vert, minVert))

At this point I tried bmesh.ops.connect_vert_pair; it seemed logical! However it's actually quite slow; even though I give it two vertices on the same face, I suspect it may be pathfinding elsewhere too. If I set faces_exclude to [all faces minus the one I want it to use], it deletes the face and doesn't connect anything. I'm not entirely sure what the exclude parameters do; there are very few examples of their use. But I did find this tiny nugget in the source code regarding them:
/* tag so we won't touch ever (typically hidden faces) */

So I wrote the following code to take each pair of vertices and join them, thus splitting the faces, checking that there's exactly one face in common with each vertex pair:
for vertTuple in vertsToJoin:
    # Could use bmesh.ops.connect_vert_pair but it takes a couple of minutes.
    # This takes about 45 seconds.

    # Find the common face, which may not be the one we had earlier
    commonFaces = list(set(vertTuple[0].link_faces) &amp; set(vertTuple[1].link_faces))
    if len(commonFaces) == 0:
        print('Vertices not on same face?')
        continue
    elif len(commonFaces) &gt; 1:
        print('Vertices on multiple shared faces!')
        continue
    face = commonFaces[0]
    firstFace = []
    inSecondFace = False
    secondFace = []
    for vert in face.verts:
        if inSecondFace:
            secondFace.append(vert)
        else:
            firstFace.append(vert)
        if vert == vertTuple[0] or vert == vertTuple[1]:
            inSecondFace = not inSecondFace
            if inSecondFace:
                secondFace.append(vert)
            else:
                firstFace.append(vert)
    bmesh.ops.delete(bm, geom=[face], context=3)
    bm.faces.new(firstFace)
    bm.faces.new(secondFace)

That last code block takes by far the longest (of the identify faces / bisect edges / split faces process), but is faster than the other face splitting methods except triangulation.